【算法專題】后綴自動機SAM


后綴自動機是用於識別子串的自動機

學習推薦:陳立傑講稿,本文記錄重點部分和感性理解(論文語言比較嚴格)。

刷題推薦:[后綴自動機初探],題目都來自BZOJ。

【Right集合】

后綴自動機真正優於后綴樹的方面在於:結合了有限狀態自動機,從而實現了O(n)的時空復雜度。

trans(s,str)表示s+str到達的狀態。

ST(str)=trans(init,str)即包括了str這一子串的唯一狀態(一個子串只能屬於一個狀態)

定義字符串a在S中出現的右端點位置集合Right(a)=r1,r2...rn

SAM中一個狀態s的Right集合為Right(s),那么狀態s表示所有[Right(a)=Right(s)的子串a],那么實際上狀態s表示的子串a的長度在一個區間內,記作[Min(s),Max(s)]。字符串越短,Right集合越大。

容易證明對於任意狀態a和b,Right(a)和Right(b)要么包含要么不相交。(很顯然的結論,只是論文的證明比較嚴格化)

★在后綴自動機中,一個狀態s(或稱“節點s”)表示的是Right(a)=Right(s)的若干子串a,其長度區間為[MIn(s),Max(s)],從而達到狀態數O(n)的目的。

Right集合形成的樹形包含關系稱為Parent Tree,樹邊由孩子指向父親,是SAM中的失配邊。

Parent樹從上往下,子串長度擴大,Right集合變小,父節點的Right集合包含子節點的Right集合

fa=Parent(s)當且僅當fa是使Right(fa)最小且滿足Right(s)⊂Right(fa)的結點,(從兒子到父親,就是子串稍微縮短,Right集合變大)

另有Max(fa)=Min(s)-1,實質上是要求fa和s之間沒有一個Right子集x滿足Right(s)⊊Right(x)⊊Right(fa)。

【線性構造SAM】

線性構造采用增量法,即已知字符串T的SAM(T),L=Len(T),在末尾加入字符x,構造SAM(Tx),轉移如下:

①實邊轉移規則:t=trans(s,x)表示s--->t標號為x的邊,若Right(s)={r1,r2...rn},則Right(t)={ri+1|s[ri+1]=x}

②虛邊轉移規則:Parent樹邊滿足Right(s)⊊Right(fa),Max(fa)=Min(s)-1(含義如上所述)

加入x,考慮所有Right集合包含L的結點v1,v2...vk,顯然它們在Parent樹上是一條從根到葉子的鏈。

定義葉子結點v1=p=ST(T)(即p代表整個串),Right(p)={L},不妨它們從后代到祖先排為v1=p...vk=root。

同時新建結點np=ST(Tx),Right(np)={L+1}。

考慮節點v,Right(v)={r1,r2...rn=L}

根據轉移規則①,如果除了rn外不存在S[ri+1]=x,那么節點v不存在trans(v,x)。

根據轉移規則②,越往上Right集合逐漸擴大直至節點vp存在trans(vp,x),那么v1~vp-1只須向np連出標號為x的邊(np=trans(v,x)),而vp~vk都已經存在trans(v,x)。

令trans(vp,x)=q,當Max(q)=Max(vp)+1時,只須在q的Right集合中插入L+1。

下面重點討論Max(q)>Max(vp)+1的情況。

eg.

T+x:aaabcaaabcaa+b

vp:aaabcaaabcaa  Max(vp)=2

q:aaabcaaabcaa   Max(q)=4

根據實邊的轉移規則,實際上節點q不止由vp轉移過來,也由vp在Parent樹上的兒子o轉移過來(多條實邊連入):

o:aaabcaaabcaa  Max(o)=3

可見,o才滿足Max(q)=Max(o)+1。trans(vp,b)形成了aab,而trans(o,b)形成了aaab,由於有限狀態自動機的性質Right(aab)=Right(aaab)所以合並成同一個點p。

但是當末尾+b時,我們發現Right(aab)>Right(aaab)也就是出現了新的Right集合aab——Right(aab)=Right(q)+Right(np),所以創造新的節點nq:

nq:aaabcaaabcaab  Max(nq)=3

Right(nq)實際上是Right(q)和Right(parent(q))夾出來的新Right集合(aab之前被並入q現在分離出來),並且vp將改為連向nq。

vp的祖先中連向q的部分要改為連向nq,因為這些點連向q說明+x后Right集合=Right(q),那么現在就要連向nq。

 

線性構造SAM的具體步驟如下:

1.新建np,找到vp,vp之前的點連向np。若不存在vp則fa(np)=-1。

2.若len(q)=len(vp)+1,fa(np)=q,結束。

3.len(q)>len(vp)+1,新建節點nq復制q信息並修改len。

4.從vp到根若連向q改為nq。

void insert_SAM(int c){
    int np=++size;
    t[np].len=t[last].len+1;
    int x=last;
    last=np;
    while(x&&!t[x].t[c])t[x].t[c]=np,x=t[x].fa;
    if(!x)t[np].fa=root;else{
        int y=t[x].t[c];
        if(t[y].len==t[x].len+1)t[np].fa=y;else{
            int nq=++size;
            t[nq]=t[y];//
            t[nq].len=t[x].len+1;
            t[nq].fa=t[y].fa;t[y].fa=t[np].fa=nq;
            while(x&&t[x].t[c]==y)t[x].t[c]=nq,x=t[x].fa;//
        }
    }
}

last=size=root=1;
for(int i=1;i<=m;i++)insert_SAM(s[i]-'a');
View Code

注意:后綴自動機節點數是2n。

【技巧和應用】

后綴自動機沒有高論,本質上僅僅是識別子串的自動機,為了壓縮時空復雜度將Right集合相同的子串集合作為一個節點,然后構造自動機的一般屬性:trans邊(實邊)和fail邊(虛邊)。

1.節點的本質:每個節點是Right集合,是Right集合相同的子串集合。

2.實邊:后接字符。

從x的某個子串后面+一個字母,能夠得到y的某個子串。(所以從根沿子串走能得到子串所在節點)

3.虛邊:前刪字符。(匹配失敗fail)

刪除最少的字符使得Right集合變化。故該串在Parent樹上的祖先都是該串的后綴。

 

1.Right集合:SAM中每個節點是right集合。

每次的np稱為“關鍵節點”,這個節點的最長字符串是以該Right點結尾的前綴。(它最終不一定是葉子)。

Right集合大小:np值為1,按Parent樹建邊進行dfs統計即可。Right集合大小就是每個狀態代表串的出現次數

例題:【BZOJ】3998: [TJOI2015]弦論

統計Right集合具體的話可以用線段樹合並或set啟發式合並。

實邊:每個節點x連出實邊y相當於在串s(x)后+x到達另一個節點(right盡可能大),這個到達的節點串的前面可能會延伸。

每一條從根到節點的路徑都是一個子串對應的狀態。虛邊:連接右端點位置相同而長度不同子串,該串在Parent樹上的祖先都是該串的后綴,這就可以用來作為fail樹。)

4.匹配:一個串從自動機根節點往下沿實邊走並cnt++,不能走就沿虛邊fail至能繼續走的點y(cnt=len(y)+1),然后往下走。因為Parent的父親一定是后綴,這樣能識別出這個串和SAM的以每個位置結尾的最長公共子串的節點。這些識別點在Parent樹上的所有祖先節點合起來就是所有公共子串。

例題:【BZOJ】4032: [HEOI2015]最短不公共子串(LibreOJ #2123)

5.后綴樹:對逆序串建立SAM,parent樹就是原串的后綴樹,后綴的LCP就是后綴樹上兩個np的LCA。

例題:【BZOJ】3238: [Ahoi2013]差異

6.找到指定串節點:在Parent上從對應右端點位置的“前綴開端節點”倍增到Len合適的位置。

例題:【BZOJ】3676: [Apio2014]回文串

7..子串數:對每個點x,Max(x)-Max(fa(x))得到的就是該點的長度區間即子串數,所有點的子串數就是子串總數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM