SAM(字尾自動機)專題總結
這是一篇其實理解並不深刻只會打板子的蒟蒻寫出的總結
分成幾個板塊吧。。。。。。
1.檢查字串是否出現
給一個文字串 \(T\) 和多個模式串 \(P\),我們要檢查字串 \(P\) 是否作為 \(T\) 的一個子串出現。
對 \(T\) 建出 \(SAM\)
直接在後綴樹上從根開始往下走,如果能走到 \(P\) 結尾說明是模板串的子串
2.不同子串個數
給一個字串 \(S\),計算不同子串的個數。
靜態:我們知道每個子串就是字尾DAG上的一條路徑
DAG上路徑數怎麼統計就不用說了吧
動態:每次新建一個節點,貢獻為\(len(np)-len(f(np))\)
好像挺顯然的?
例題:生成魔咒
3.最小迴圈移位
給定一個字串 \(S\) 。找出字典序最小的迴圈移位
發現字串 \(S+S\) 包含字串 \(S\) 的所有迴圈移位作為子串。
所以問題變為在 \(S+S\) 對應的字尾自動機上尋找最小的長度為 |S| 的路徑
直接從初始狀態開始,貪心地訪問最小的字元即可。
例題:工藝
4.出現次數
對於一個給定的文字串 \(T\) ,有多組詢問,每組詢問給一個模式串 \(P\) ,回答模式串 \(P\) 在字串 \(T\) 中作為子串出現了多少次
我們發現用如果模式串在 \(SAM\) 上跑匹配,那麼最終到達的點的 \(endpos\) 就是該串的出現次數
考慮 \(endpos\) 的處理根據定義發現其實就是字尾樹上的子樹大小
所以我們把實點權值設為 \(1\),虛點設為 \(0\),跑拓撲就行了
例題:\(substring\)(需要動態維護 \(endpos\),打棵 \(lct\) 唄)
5.字典序第 k 大子串
給定一個字串 \(S\) 。多組詢問,每組詢問給定一個數 K ,查詢 \(S\) 的所有子串中字典序第 K 大的子串。
字典序第 \(K\) 大的子串對應於 \(SAM\) 中字典序第 \(k\) 大的路徑
那麼在計算每個狀態的路徑數後,就可以從 \(SAM\) 的根開始找到第 \(k\) 大的路徑。
例題:弦論
6.第一次出現的位置
給定一個文字串 \(T\) ,多組查詢。每次查詢字串 \(P\) 在字串 \(T\) 中第一次出現的位置( \(P\) 的開頭位置)。
預處理出每個狀態第一次出現的位置\(pos(i)\)
其實只需要讓每次\(pos(np)=len(np) \ pos(nq)=pos(q)\)就好了
查詢答案為\(pos(i)-|T|+1\)
7.最短的沒有出現的字串
給定一個字串 \(S\) 和一個特定的字符集 \(T\),我們要找一個長度最短的沒有在 \(S\) 中出現過的字串
在 \(SAM\) 上做 \(dp\)
設 \(dp_i\) 表示到點 \(i\) 時的最短長度
如果這個點有不是 \(T\) 中字元的出邊,則 \(dp_i=1\),否則 \(dp_i=1+\min\limits_{(i,j,c)\in SAM}dp_j\)
8.兩個字串的最長公共子串
給定兩個字串 \(S\) 和 \(T\) ,求出最長公共子串。
直接把 \(T\) 扔到 \(S\) 的自動機上跑匹配就行了
9.求 \(endpos\) 集合
給定一個字串 \(S\),求 \(endpos\) 集合
首先我們能夠求出每個節點的 \(pos\)
然後發現一個點的 \(endpos\) 就是他子樹的 \(pos\) 的集合
怎麼讓一個點帶上整個子樹的某個值? 主席樹合併啊!
每個點初始在主席樹上插入 \(pos(i)\)
然後拓撲合併就行了
例題:你的名字