不想學博弈論不想學 sa 不想學插頭 dp,學 lct 被 axdea d 飛了,那就來學 sam。
sam 是字尾自動機,名義上是字尾,但實際上它能表示出乙個字串的所有不同子串。不同於你的 \(o(n^2)\) 列舉,sam 構造,節點和邊的數量也都是 \(o(n)\) 級別的。
更具體的,sam 表現為一張 dag,每條邊上有乙個字母(類似於 ac 自動機和 trie),然後從源點開始到任意節點結束都能形成乙個字串,而這些字串剛好都是原來字串的子串,並且已經包含了所有的子串。
在構造 sam 之前,需要知道一些定義。
我們對於乙個字串 \(s\) 的任意乙個子串 \(t\),那麼 \(t\) 在 \(s\) 中所有出現的位置的結束位置下標的集合叫做 \(t\) 的 endpos。
我們把 endpos 相同的串的集合稱作乙個 endpos 等價類,簡稱類。
在構造 sam 之前,需要知道一些引理。下面我們認為 \(s_1\) 和 \(s_2\) 是原串 \(s\) 的兩個不同子串,並且有 \(|s_1|\le |s_2|\)。
\(s_1\) 是 \(s_2\) 的字尾 \(\rightarrow\)
\(\operatorname(s_2)\subseteq\operatorname(s_1)\)
\(\operatorname(s_2)\cap\operatorname(s_1)\not=\varnothing\)
\(\rightarrow\)
\(\operatorname(s_2)\subseteq\operatorname(s_1)\),且 \(s_1\) 是 \(s_2\) 的字尾
在乙個類中,找出其長度最大的串 \(u\),那麼剩下的串長度從 \(u\) 開始遞減。
根據lemma 1.2可以發現,這些子串之間形成了一棵樹形結構。我們把這個樹形結構叫做 parent tree。
更具體地,考慮乙個類中最長串 \(maxs\),如果我們向這個串的前面加入乙個字元,得到乙個字串 \(news\),那原來類中元素的 endpos 中的位置分成了兩種,一種是在 \(\operatorname(news)\) 中的位置,一種是不在其中。然後我們在 \(maxs\) 前面加上不同字元就把 endpos 中的位置分成了 \(\sum\) 種(\(\sum\) 是字符集),作為這個類的兒子節點(空的刪掉),然後就構成了這棵 parent tree。
一棵 parent tree
一些說明:上面節點中的 \(s\) 表示這一類中的 \(maxs\),也就是最長的串。
反正還是看圖理解吧。那這樣一棵樹有什麼用呢?其實在後面給出 sam 的構造的時候會說,我們對於乙個節點需要能夠找到它在 parent tree 上的父親。而且sam 上節點的意義和 parent tree 節點的意義是相同的,使得其節點數也和 parent tree 相同,從而保證複雜度(見下方引理)。
其實不難發現通過列舉在 \(maxs\) 前面加的點來構造這棵樹的過程,是能夠不重不漏地表達出所有原串的子串的,這和 sam 的性質相同。
所以 sam 是基於 parent tree 構造的。
接下來有幾個引理:
對於 parent tree 上的節點 \(u\) 和它的乙個兒子 \(v\),\(u\) 中的 \(maxs\) 的長度加一就是 \(v\) 中的 \(mins\) 的長度。
parent tree 的點數和邊數都是 \(o(n)\) 級別的。
先來看**吧。
struct nodetr[maxn<<1];
int tot,lst;
void ins(int c)
}}
首先,sam 是用增量法構造的,就是通過在後面加入乙個字元,然後插入新字串的所有字尾。這樣最終必然能表出原串的所有子串。
然後我們考慮插入這些字尾的時候會對 parent tree 上哪些節點產生影響。很容易可以想到,就是節點表示的 endpos 中包含了原來的長度的那條鏈。比如,對於上面那個圖,\(\\to\\to\\to\\) 這一條鏈,在插入第 \(8\) 個字元的時候會有影響。我們把這條鏈叫做終止鏈。
然後我們考慮這多出來的乙個字元會產生什麼影響。首先對於自動機而言,我們從終止鏈底向上跳,每個節點都要向新建的節點連一條邊上是字元 \(c\) 的邊。那如果一路暢通,那麼皆大歡喜,沒什麼影響,只要在 parent tree 的根上多連乙個兒子就行了,相當於是出現了乙個陌生的字元,這是第一種情況;第二種情況是如果我們向上跳的過程中有乙個節點 \(p\),已經有邊上是 \(c\) 的邊連到另乙個節點 \(v\) 了,說明有重複的子串了。那這個時候我們需要多維護乙個東西來進一步地分討。
我們維護每個節點的 \(maxs\),**中記為 \(len\)。在上面這種情況下,如果有tr[p].len+1==tr[v].len
,那麼說明,在 parent tree 中,還沒有比加字元產生的新字尾更長的,並且同樣屬於 \(v\) 的乙個字尾。
SAM 學習筆記
詳見 text 字串 s 的 text 是乙個接受 s 所有字尾的最小dfa 確定性有限自動機或確定性有限狀態自動機 其中,s 每個字尾均可用一條從初始狀態 t 到某個終止狀態的路徑構成。包含 s 的所有子串 從 t 開始的任意路徑都構成乙個子串,每個子串也對應某條路徑。但是到某個狀態的路徑可能不止...
SAM初學筆記
我第一次學習sam,可能有很多地方理解有偏差或者錯誤。還請各位大佬指正。這篇文章中沒有嚴謹的證明,只有感性的理解。追求嚴謹的請轉他處。首先我們定義parent樹 乙個節點與其所代表的字串的最長的,且出現次數與其不一樣的字尾連邊,所形成的樹是parent樹。可以發現parent樹有很多優秀的性質,比如...
SAM學習小記
只是乙個小記,不是演算法詳解 參考資料 史上最通俗的字尾自動機詳解 廣義sam模板題解 簡單的,乙個有向無環圖,邊有字母,滿足起點開始的每一條路徑都是原串的乙個子串。並且保證複雜度在o n o n o n 級別內的。每乙個子串p pp的end pos p endpos p endpos p 被定義為...