1. 程式人生 > >SAM(字尾自動機)專題總結

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)\)

然後拓撲合併就行了

  例題:你的名字