【知識總結】字尾自動機的構建
參考資料:(APIO2018)從DFA到字尾自動機_張雲帆
又一個學了很多遍都不會的演算法/資料結構……(話說我怎麼每篇知識總結一開始都是這句話qwq)
先orz字尾自動機之神兔崽子TzzDzz(順便餵它最喜歡吃的葉子)
OrzTzzDzz
前排提示:由於作者很菜,且本文的目標是快速理解並寫出 (背過) 字尾自動機的構建過程,所以將會省略很多結論的證明,語言也有不嚴謹之處,敬請諒解。如感興趣可以參考文首提到的參考資料。閱讀前建議先了解AC自動機。
(字尾自動機之神Tzz自己把字尾自動機玩得爐火純青,給別人講時卻就讓別人背程式碼。如果您不幸受到了Tzz的教育 (誤導),請可以直接拉到最下方看程式碼然後把他嘴一頓
零、前言
很多字串題要處理與子串有關的問題。如果想在AC自動機上處理一個字串的所有子串,通常要將它的所有後綴插入(子串就是字尾的字首),時空複雜度是\(O(n^2)\)的。於是,我們需要充分利用插入的所有串都是同一個串的字尾這一性質來優化。字尾自動機,請。
(其實是Tzz太神仙了,自稱不會KMP和AC自動機,見到所有字串題都字尾自動機+雜湊秒掉)
一、字尾自動機的相關定義和性質
以字串"aabab"為例(下標從\(0\)開始)。
一個字串的\(Right\)集合:該字串所有出現位置的右端點的集合。如字串"ab"的\(Right\)集合是\(\{2,4\}\),字串"a"的\(Right\)
等價:如果兩個字串的\(Right\)集合相同,那麼稱它們“等價”。如字串"ab"和"b"的\(Right\)集合都是\(\{2,4\}\),所以它們是等價的。
和AC自動機相比,字尾自動機把所有等價字串都縮成一個結點(即每個結點表示一個\(Right\)集合,點\(u\)的\(Right\)集合記作\(R_u\)),是一個DAG(而不是像Trie樹一樣的樹形結構)。稍後將證明,結點數不會超過\(2n+1\)
一個結點的\(fa\):兩個結點\(u\)和\(v\),若\(R_v\)是\(R_u\)的真子集,且\(u'\)是所有這樣的結點\(u\)中\(Right\)集合的元素最少的,則\(u'\)是\(v\)的\(fa\)。根(空串)的\(fa\)不存在。根據三分律,除了根外所有節點有且只有一個\(fa\),所以會組成一棵\(fa\)樹。
下面是對"aabab"建出的字尾自動機(鼠繪)(黑線是自動機的轉移邊,紅線是\(fa\),綠字是該結點對應的子串)。可以看出,所有子串都對應一個結點,但一個結點可能對應多個子串(圖中用逗號分隔)。
請在可以獨立畫出這幅圖後再繼續閱讀。
結點的\(min\)和\(max\):該結點對應的子串長度的最小/最大值。例如上圖中\(\{4\}\)的\(min\)是\(3\),\(max\)是\(4\)。可以證明,結點對應的子串長度一定是一個連續的區間\([min,max]\)。並且,如果\(p\)是\(q\)的\(fa\),那麼有\(max_p\)=\(min_q-1\)。(兩個結論都可以反證。)腦補一下可以發現,\(max\)和\(min\)分別相當於從根節點到該結點的最長路和最短路的長度。不可能有兩條路長度相等(想一想,為什麼)。
下面來證明上面“稍後再證”的兩個結論。
三分律:反證。設兩不同狀態為\(p\)和\(q\),則三分律可表示為:\(R_p\cap R_q=\emptyset\)、\(R_p\subset R_q\)、\(R_p\supset R_q\)中有且只有一式成立。假設\(R_p \cap R_q \neq \emptyset\),任取結束位置\(r\in R_p \cap R_q\)。根據“一個子串只對應一個結點”,\(p\)和\(q\)對應的子串沒有交集,所以\([min_p,max_p]\)和\([min_q,max_q]\)沒有交集。假設\(max_p<min_q\),由因為它們具有相同的結束位置(\(r\)集合中的元素),則\(p\)對應的串是\(q\)對應的串的字尾。所以\(R_p \subset R_q\)。同理,當\(max_q<min_p\),能推出\(R_p \supset R_q\)。
結點數不超過\(2n+1\):設根結點\(Right\)集合大小為\(i\)的\(fa\)樹的最大結點數為\(f(i)\),則\(f(i)=\sum_{j=1}^k f(x_j)+1(\sum_{j=1}^k x_j\leq i)\)。由於\(f(x_j)\)都是正的,所以就相當於\(f(i)\leq \sum_{j=1}^k f(x_j)+1(\sum_{j=1}^k x_j=i)\)。當\(i=1\)顯然滿足\(f(i)=1=2n-1\),當\(i=1\)時腦補一下歸納,若對於任意\(f(j)(j<i)\)都成立,則有\(f(i)\leq \sum_{j=1}^k f(x_j)\leq 2\sum_{j=1}^k x_j-k<2i-1\)。而根節點(空串)\(Right\)集合結點數為\(n+1\),所以結點數不超過\(f(n+1)=2(n+1)-1=2n+1\)。
轉移數不超過\(3n\):首先求一棵自動機的樹形圖,邊數是點數減\(1\),即不超過\(2n\)。(證明待續……)
二、字尾自動機的構建
使用增量法。每次插入一個字元。
設正在插入第\(n\)個字元\(c\)。\(n\)位置在當前字尾自動機中沒有出現過,所以不會造成已有狀態的合併,且一定會出現(至少)一個新結點,對應\(Right\)為\(\{n\}\),對應的最長串長度(\(max\))為\(n\)。
更進一步考慮新插入的結點會影響到原來的哪些結點。新字元只會與以\(n-1\)結尾的子串形成新的子串,所以受影響的一定是\(Right\)集合包含\(n-1\)的結點,即\(Right\)集合為\(\{n-1\}\)的結點(一定存在)在\(fa\)樹上到根的鏈。同時,如果這些結點本來就有\(c\)字元的轉移邊,那麼這些轉移邊指向的結點也會受影響(可能\(Right\)集合會增大)。下面無特殊說明的情況下,原串指前\(n-1\)個字元組成的字串,\(np\)是新建的\(Right\)為\(\{n\}\)的結點,\(p\)是一個\(Right\)集合包含\(n-1\)的結點,\(q\)是\(p\)的\(c\)字元轉移邊指向的點(如果存在)。
如果\(p\)沒有\(c\)字元的轉移邊,由於現在有了,直接向\(np\)連邊即可。
如果一直到根都沒有\(c\)字元的轉移邊,說明\(c\)字元在前\(n-1\)個字元中沒有出現過。把\(np\)的\(fa\)置為根,插入結束。
如果\(p\)可以通過\(c\)字元轉移到\(q\),且深度最大,那麼進行如下討論。
狀態的“分裂”是指由於加入了新的字元導致原來等價的字串不再等價。如"aabca"中"aab"、"ab"和"b"是等價的,在後面加入一個'b'後"ab"和"b"等價,但不與"aab"等價。腦補一下,分裂時分出的每個部分的長度都是連續的區間,字串長度越短\(Right\)集合越大。
前面提到過,\(max_p\)就是根到\(p\)的最長路,所以\(max_q\geq max_p+1\)。
如果\(max_p+1=max_q\),說明原來\(p\)中對應的最長串後面加一個字元\(c\)形成了\(q\)對應的最長串。所以,所有\(p\)最長串的字尾加上一個字元\(c\)都是\(q\)最長串的字尾。\(q\)對應的子串集合是這些字尾的子集。因此\(q\)不會分裂,只是\(Right\)集合中增加了一個\(n\)。
如果\(max_p+1<max_q\),說明在\(q\)的最長串不是由\(p\)途徑轉移而來的,所以\(q\)的最長串去掉最後一個字元\(c\)不是原串的字尾。加入一個字元\(c\)後,只有從\(p\)轉移到\(q\)的子串的出現位置會增加,而比\(max_p+1\)還長的那部分不會變。所以此時\(q\)分裂為兩個結點。稱\(Right\)集合更新(即包含\(\{n\}\)的結點)為\(nq\)。
腦補一下,\(nq\)的\(Right\)集合是原來\(q\)的\(Right\)加上\(n\),所以它應當成為\(q\)的\(fa\)。而\(nq\)的\(fa\)應當是\(q\)原本的\(fa\)。因為\(nq\)中所有串都是\(p\)加上一個字元\(c\)得到的,所以\(max_{nq}=max_p+1\)。同時,\(p\)到根的路徑上所有能轉移到\(q\)的結點都應該改為轉移到\(nq\)(轉移到\(q\)在\(fa\)樹上的祖先的點不理。這樣修改的點一定是鏈上連續的一段)。
這個構造方式的時間複雜度均攤是\(O(n)\)的(然而並不會證明
完結撒花。