看了幾天的后綴自動機,感覺這玩意兒確實比較神奇。但是感覺自己肯定講不明白,就簡單的來寫寫心得和應用吧
性質
1、每個狀態$s$代表的長度區間為$(len[fa[s]],len[s])$
也就是說$min(s) = max(s) + 1$
2、每個狀態$s$代表的所有串在原串中的出現次數及出現位置右端點相同。
這也是后綴自動機能夠壓縮狀態的原因,就是把很多相同的串壓縮到一個節點中
3、在parent樹中,對於狀態$s$,$fa[s]$所代表的狀態是$s$所代表狀態的后綴
4、在parent樹中,每個狀態的$right$集合是它父節點$right$集合的子集
由性質$3$很容易得到$fa[s]$所代表的狀態是$s$所代表狀態的后綴,那么$fa[x]$所代表的子串的出現位置一定比$s$代表子串的出現位置多
5、對轉移邊形成的DAG拓撲排序后,每個節點對應的大小為:以該節點為起點的子串的數量(本質相同的子串算一個)
6、對$fa$邊形成的樹拓撲排序后,每個節點對應的大小為該節點對應$right$集合的大小
$fa[s]$表示的是$s$的前綴,那么$s$出現的地方$fa[s]$也一定出現
經典應用
求兩個串的最長公共子串
首先把第一個串的SAM建出來,
枚舉第二個串,同時沿着轉移邊進行匹配,若匹配失敗,那么就沿着$fa$邊向上走,
匹配的同時記錄一下$max$
求多個串的最長公共子串
網上的做法基本都是對第一個串建SAM,然后枚舉其他的串,在這個串上匹配。
但是貌似要考慮很多東西??
這里我將一個比較naive但是不會TLE的做法。
最終的答案一定出現在第一個串中,所以可以把第$2-N$個串的SAM建出來
然后枚舉第一個串的每一位,在其余的SAM中匹配。
雖然聽着嚇人,但是代碼十分好寫
求第$k$小子串
我們可以通過對轉移邊$dfs$而求出以該節點為起點的子串的大小
開始時從$root$開始走,每次優先選擇字典序小的轉移邊,
若該出邊對應的大小$<k$,說明答案不在該出邊所對應的字符串中,令$k$減去該節點的大小,繼續匹配
若該出邊對於的大小$>=k$,說明答案在該出邊中,那么沿着該出邊繼續走
注意在求第$k$小子串的時候要考慮本質相同的子串是否重復統計的問題
如果要重復統計,則加上$right$集合的大小就可以了
字符串最小表示法
最小表示法的定義:
字符串$S$的最小表示法為,對於任意的$i \in [1, |S|]$,把$[1,i]$對應的字符串剪切到$S$尾所形成的字符串中,字典序最小的一個
字符串的最小表示有它自己的算法,可以參考這里
當然后綴自動機也是可以搞的,我們首先把字符串復制一遍,扔到SAM里,
然后從根節點出發貪心的走較小的出邊,同時輸出每一次經過的字符,當達到$N$次時停止。
但是我還是建議大家學一下最小表示法的標准算法, 因為后綴自動機需要$4*|S|*siz$的空間($siz$表示字符集),很容易被卡掉
求本質不同的串的數量
考慮到每個狀態表示的子串是兩兩不同的,
根據性質1每個狀態$s$代表的長度區間為$(len[fa[s]],len[s]]$
然后對所有節點求個和就好,答案為$\sum_{t} len[t] - len[fa[t]]$