SAM学习小记
前言
只是一個小記,不是算法詳解
參考資料
史上最通俗的后綴自動機詳解
廣義SAM模板題解
正題
概念
定義
簡單的,一個有向無環圖,邊有字母,滿足起點開始的每一條路徑都是原串的一個子串。
并且保證復雜度在O(n)O(n)O(n)級別內的。
endposendposendpos相關
每一個子串ppp的endpos(p)endpos(p)endpos(p)被定義為原串中所有出現子串ppp的末尾位置集合。
endposendposendpos相同的子串被定義為一個endposendposendpos類,可以證明任何一個串的endposendposendpos類數量不會超過O(n)O(n)O(n)級別。
性質:
- 對于兩個endposendposendpos相同的子串,其中短的一個一定是另一個的后綴
- 對于任意兩個子串p,qp,qp,q,且qqq的長度大于ppp,一定有endpos(p)?endpos(q)endpos(p)\subseteq endpos(q)endpos(p)?endpos(q)或endpos(p)∩endpos(q)=?endpos(p)\cap endpos(q)=\varnothingendpos(p)∩endpos(q)=?
parentsparentsparents樹
根據endposendposendpos的兩個性質我們可以構建一棵樹,其中對于兩個endposendposendpos類x?yx\subseteq yx?y那么就有一條邊x?>yx->yx?>y。
性質:
- 對于一個在faxfa_xfax?的endposendposendpos類里的串一定是xxx的endposendposendpos類串的后綴
- 定義在faxfa_xfax?的endposendposendpos類里的最長的串長度為LenLenLen,在endposendposendpos類里最短的串長度為lenlenlen那么有Len=len+1Len=len+1Len=len+1
還有一個最重要性質,就是parentsparentsparents里的節點就是SAMSAMSAM里的節點
代碼解析
懶得搞圖,圖片去看參考資料。
void add(int c){int p=las;int np=las=++cnt;len[np]=len[p]+1;siz[cnt]++;for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;if(!p)fa[np]=1;else{int q=ch[p][c];if(len[p]+1==len[q])fa[np]=q;else{int nq=++cnt;len[nq]=len[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[nq]));fa[nq]=fa[q];fa[np]=fa[q]=nq;for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;}}return; }- 第333行:新節點的最長一定新串的長度,因為左右不可能再加任何節點。
- 第444行:往前跳,中途指一條ccc的邊向新節點npnpnp(更新路徑上的endposendposendpos)。但是如果一個位置已經有這條邊了,前面已經加入過這個字符即有這些新的endposendposendpos了。在parentparentparent樹上跳的原因是因為這條鏈上的endposendposendpos是包含后綴的。
- 第555行:如果字符ccc是新出現的字符,那么這一定是一個由根節點印出來的新endposendposendpos類。
- 第888行:定義新的ppp為p′p'p′,因為p′p'p′是ppp的祖先,且p′p'p′中最長的長度加一是ppp中最長的,那么也就是qqq中最長的與npnpnp擁有相同的endposendposendpos即endpos(np)?endpos(q)endpos(np)\subseteq endpos(q)endpos(np)?endpos(q)那么npnpnp的父節點就是qqq
- 第10~1310\sim 1310~13行:這是SAMSAMSAM中最關鍵的部分,當lenp+1len_p+1lenp?+1不等于lenqlen_qlenq?時,那么表示lenqlen_qlenq?中不能所有的endposendposendpos都加入nnn,而是有一部分要分離出來。此時我們需要新建立一個節點nqnqnq來儲存加入了nnn這個endposendposendpos的endposendposendpos類。首先加入新字符之前nqnqnq與qqq完全相同,所以我們可以直接繼承qqq的信息。而因為nqnqnq是可以加入nnn這一信息的,所以有endpos(q)?endpos(nq)endpos(q)\subseteq endpos(nq)endpos(q)?endpos(nq)所以我們讓qqq的父節點為nqnqnq,然后顯然npnpnp的父節點也為nqnqnq。然后我們要往前更新信息,如果跳到chp,c≠qch_{p,c}\neq qchp,c??=q時,這個節點ppp本身ccc的出邊時本身就與qqq沒有關系的,所以就可以不用再更新了。
廣義SAM
如果一個SAMSAMSAM中我們要加入多個字符串時我們應該怎么辦,廣義SAMSAMSAM分為離線的和在線的做法。
離線做法:對于所有的字符串我們先構造出一棵TrieTrieTrie樹,然后每一次的lastlastlast就是它TrieTrieTrie樹上的父節點的節點,因為這樣保證了一個節點后面不會插入重復的字符。
在線做法:考慮如果插入了重復的字符怎么辦,那么證明這個節點的endposendposendpos類一定是有出現過的,我們可以像SAMSAMSAM中后面那個判斷一樣搞就好了
這里是在線做法的代碼:
例題解析
P3804-[模板]后綴自動機
給出一個長度為nnn的串,求一個最大的子串長度乘上子串出現次數
在構造SAMSAMSAM的時候統計每個endposendposendpos類包含了幾個endposendposendpos,這就是這個endposendposendpos類子串中出現的次數,然后每個endposendposendpos類取最長的子串就好了(在構造時已經保存了)。
對于每個endposendposendpos的集合如何統計,對于每個npnpnp,它都會有一個前綴iii作為這個endposendposendpos類,而在往下的過程中這個前綴位置iii會丟失(因為再往下的末尾都是在iii之后,而沒有一個在iii的后綴是在之后加入的)。也就是讓fnp++f_{np}++fnp?++,然后按照長度從大到小讓ffax+=fxf_{fa_x}+=f_{x}ffax??+=fx?,因為一個節點的父節點長度一定比子節點小。
P3975-[TJOI2015]弦論
求一個字符串第kkk大的子串。
對于相同的子串算同一個時,那么其實就是求SAMSAMSAM上字典序第kkk大的路徑,處理出每一個節點的后繼狀態直接跑即可。
對于相同的子串不算同一個時,我們處理處每個節點的endposendposendpos集合大小,這就是這個endposendposendpos類中每個子串的出現次數。然后用這個處理后繼狀態直接跑即可。
SP1811-Longest Common Substring
求兩個串的最長公共子串
我們先構建第一個串的SAMSAMSAM,然后用第二個串在第一個串上跑,如果不能繼續跑下去那么我們直接在parentparentparent樹上往前跳就好了,因為在SAMSAMSAM中parentparentparent樹的左右可以類似于ACACAC自動機中failfailfail樹的定義。然后跳完了之后取len+1len+1len+1就好了。
P5341-[TJOI2019]甲苯先生和大中鋒的字符串
求出現次數恰好為kkk的字符串中出現次數最多的長度
因為對于一個相同的endposendposendpos類中的字符串都是長度連續且不同的,也就是[lenfax+1,lenx][len_{fa_x}+1,len_x][lenfax??+1,lenx?]這個范圍內每一個長度各有一個字符串,然后差分做就好了。
總結
- 上一篇: P3168-[CQOI2015]任务查询
- 下一篇: [2020.11.25NOIP模拟赛]出