後綴自動機學習筆記
參考:https://blog.csdn.net/doyouseeman/article/details/52245413
https://wenku.baidu.com/view/90f22eec551810a6f4248606.html
APIO2018課件《從DFA到後綴自動機》張雲帆
隨便寫一點。
定義一個子串的right集合為這個子串在原串中出現的右端點集合。
SAM作為一個自動機,自然是一個DAG。其中每個點表示一個right集合。邊表示“給其起點的right集合所表示的子串‘添上該邊所表示的字符’能夠擴展到其終點的right集合”。令len表示一個點表示的最長子串長度。
可以發現原串中會有一些不同的子串有相同的right集合,若其相同,一定存在某子串是另一子串的後綴。並且如果給他們添上同一個字符,能拓展到的狀態是相同的。
並且還會有一些點的right集合被另一些點包含。若其包含,則顯然right集合元素個數較多的點表示的子串為被包含點的後綴。
類似於AC自動機,給SAM定義fail指針。令每個點的fail指針指向“所有包含其right集合的點中‘right集合元素個數最少’的點”,若不存在則指向初始狀態(空串,其right集合可視為0~n)。顯然被包含點所表示的最短子串的長度=fail指向點表示的最長子串的長度+1。如果在某個點無法繼續匹配下去,就可以跳轉到fail指向的點,因為其right集合更大,選擇更多。
將fail指針反向後形成的樹稱為parent樹。因為對於parent樹上的點,其父親表示的子串為其後綴,那麽這棵parent樹其實就是將原串的所有前綴取出並翻轉後建出的trie,更簡單一點地看就是原串的反串的後綴樹。並且兩個子串的最長公共後綴就是其lca表示的子串中最長的。
接下來考慮構造SAM。構造算法是在線的,即在構造了i-1前綴的SAM的基礎上插入s[i]。
為s[i]新建一個節點x,此時表示right集合僅為i的子串。考慮將x加入SAM中。將上一個這樣的節點(即表示right集合僅為i-1的節點)稱為last。顯然需要由last向x連一條邊,表示添加上字符s[i]後可以從last轉移到x。
然後繼續考慮還有哪些狀態可以轉移到x。顯然既然這樣的狀態表示的子串加上s[i]後可以成為一個i前綴的後綴,那麽當前其就是i-1前綴的後綴,即right集合包含i-1,而right集合包含i-1的點一定是last的祖先。
於是對於這些點,如果當前其沒有s[i]邊,給它添加s[i]邊連向x;否則此時其s[i]邊指向的點的right集合相當於已包含了i,不用再連邊。並且顯然若某個點存在s[i]邊,其祖先也一定存在s[i]邊。那麽從last節點沿著fail指針不斷往上跳並向x連邊,直到到達存在s[i]邊的節點時停止(或根)就可以了。
對於原本就已有s[i]邊的點,其s[i]邊所連向點的right集合應已包含i,但還並沒有對其做出修改。設第一個找到的存在s[i]邊的節點為p,其s[i]邊所連向點為q。有lenq>lenp,因為q的right集合顯然在p的right集合+1中。
考慮給q的right集合中加入i造成的影響。可以發現當新加入的後綴子串無法滿足len的限制時,這會造成q狀態的分裂。分兩種情況。
若lenq=lenp+1,則其不會分裂。這顯然成立。q的right集合是除x外最小的包含i的right集合,因為p的祖先的right集合比p更大,後繼狀態的right集合也會更大。於是可以把x的fail指針指向q。同樣由於這一點,對於p的祖先的後繼狀態不會產生影響,因為其right包含了q的right且len更短。
而若lenq>lenp+1,就需要進行分裂了。顯然其後綴添加s[i]後只有len<=lenp+1的部分能與其他子串相同,否則與p的right集合是極小的真包含i的集合矛盾。於是以子串長度小於等於lenp+1(包含後綴)和大於lenp+1(不包含後綴)為界線分割。對於小於等於lenp+1的部分,新建點y,將leny設為lenp+1,其余與q相同。而大於lenp+1的部分留在q點。然後考慮fail指針。由於y的right集合僅比q多了一個i,q的fail指針指向y。同時與之前的情況類似,x的fail指針也指向y。y的fail指針指向原q的fail指針所指向點,因為它們的right同時添加了i。之後需要將p及其祖先中指向q的點改為指向y。感覺這裏有一堆搞不懂為什麽的東西不過可能搞懂了也沒啥用那就棄療吧。
後綴自動機學習筆記