後綴自動機(知識整理+板子總結)

思路來源

https://www.luogu.com.cn/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie 首推這一篇

https://www.cnblogs.com/zjp-shadow/p/9218214.html 也看了這一篇,也不錯

http://hihocoder.com/problemset/problem/1441 hihocoder板子題

https://www.luogu.com.cn/problem/solution/P3804 洛谷板子題

前置知識

自動機的知識(編譯原理那一套,比如有限狀態自動機云云,知道能在其上沿邊轉移感覺就可)

AC自動機、Trie樹、後綴數組

 

死記硬背環節

證明和心得(後附)感覺用處不大,直接計入死記硬背和抄板子做題環節

後綴自動機(SAM),是一個最小的確定有限狀態自動機(DFA),接受且只接受S的後綴

parent樹和自動機節點共用,時間複雜度O(n),空間複雜度O(n)

 

parent(也稱fa,後綴鏈接link):當一個節點的串,在變爲其後綴,且endpos發生了擴大時,最長的那個後綴對應的節點

endpos(也稱right):每個節點對應的串的endpos集合是一致的,每個子串唯一出現在某個節點裏

len:一個節點內代表的串的最大長度

minlen:一個節點內代表的串的最小長度,由於a回跳到父親fa(a)的時候len(fa)+1=minlen(a),故一般不存

ch[0-26](後綴轉移transfer):自動機上對應字母的轉移

 

配合兩張圖食用,便於記憶這些概念

圖1:abcd的後綴自動機,

紅色括號是endpos集合,字母是自動機轉移

圖二:aaba的後綴自動機,

紅色數字是節點對應的最長子串的長度len,藍邊是其parent樹上的父親fa

板子

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct NODE
{
    int ch[26];
    int len,fa;
    NODE(){memset(ch,0,sizeof(ch));len=0;}
}dian[N<<1];
int las=1,tot=1;
void add(int c)
{
    int p=las;int np=las=++tot;
    dian[np].len=dian[p].len+1;
    for(;p&&!dian[p].ch[c];p=dian[p].fa)dian[p].ch[c]=np;
    if(!p)dian[np].fa=1;//以上爲case 1
    else
    {
        int q=dian[p].ch[c];
        if(dian[q].len==dian[p].len+1)dian[np].fa=q;//以上爲case 2
        else
        {
            int nq=++tot;dian[nq]=dian[q];
            dian[nq].len=dian[p].len+1;
            dian[q].fa=dian[np].fa=nq; 
            for(;p&&dian[p].ch[c]==q;p=dian[p].fa)dian[p].ch[c]=nq;//以上爲case 3
        }
    }
}
char s[N];int len;
int main()
{
    scanf("%s",s);len=strlen(s);
    for(int i=0;i<len;i++)add(s[i]-'a');
}

性質

①endpos:一個子串在原串中所有出現的地方,其末位置下標的集合

如串abcab,endpos(ab)={2,5}

 

②endpos相同,一個必爲另一個後綴,

考慮abcdbcd,當abcd變成後綴bcd的時候,endpos增加,bcd變成cd的時候,endpos不變

 

③根據②,任意兩個子串的endpos集合,要麼一個是另一個子集,要麼二者相交爲空,

只要有一個相交的位置,說明是後綴,後綴就是含於的情況

 

④根據②,在子串縮短爲其後綴的過程中,要麼endpos不變,要麼增加,把endpos相同的視爲一個等價類節點

在SAM中,一個子串只屬於一個節點,這個節點內的子串的endpos都相同

 

⑤endpos等價類(節點)個數爲O(n),

這個沒看懂曾經看懂過,可以參考原博客

 

⑥parent樹、自動機

後綴自動機的parent樹和自動機的節點是共用的,

parent樹往祖先跳的時候,endpos會變大,實際上是當前串變爲其後綴串,在節點中用fa定義

自動機就是讀進這個字符轉移到哪個點,定義了一種轉移關係,在節點中用ch[0-26]定義,類似trie

 

⑦由②,不難發現,endpos不變的一段子串是連續的,

分別記minlen和len爲這個點對應的子串的最小長度和最大長度

記點a覆蓋的串的長度爲[minlen(a),len(a)],則從minlen(a)再縮短一個長度的時候,endpos擴充,跳到父親

所以有len(fa(a))+1=minlen(a),fa(a)是a在parent樹上的父親

 

⑧後綴自動機的邊數爲O(n)

這個沒看懂曾經看懂過,可以參考原博客

 

⑨SAM的構造過程

設當前字母爲c,從上一個點las轉移過來,當前新開一個點np,endpos多出一個{n}來

先考慮轉移,爲了最大程度的利用節點,

只對las的祖先節點中,計當前祖先節點爲p,

若不存在字母c的轉移的點,對其進行轉移到np的操作,

並令p回跳到p的父親,最終p停留在了某個節點

 

以下分三種情況,實際上是(1)(2)兩種情況,其中(2)分爲兩種子情況

 

(1)c是沒出現過的字符,這樣回跳的時候一定會回到根,這裏計根爲1號節點

根代表了空串,其endpos集合爲{1,2,...,n},

因此,c所在節點np構成了一個新的endpos集合{n},直接令fa(np)=1

 

(2)停留在了中途某個點,此時點p存在字母c的轉移,所以停下了,

這其中分成兩種子情況,計q爲p通過字母c能轉移到的點,len[q]是q中最長串的長度

 

①若len[q]=len[p]+1,由於p是las的祖先,p的串一定是las串的後綴,

np比las多了一個字母c,q比p多了一個字母c,且q最長的串是p最長的串的長度+1,

這表明,在p的串後面補一個字母c,其仍然是 在las串後面補一個字母c 的後綴,

則q的串也是np串的後綴,q的endpos一定是np的endpos的超集,令fa(np)=q即可

 

②len[q]>len[p]+1,由於len[q]>=len[p]+1(是由p的串補了一個字母c而來),既然不等於,就一定大於

說明q中的串分成了兩部分,長度=len[p]+1的那些串x,由①,是np串的後綴,其endpos多出了{n}

長度>len[p]+1的那些串y,不是np串的後綴,其endpos沒有變化,

而q是np在往上回跳的過程中,第一個包含了np的所有endpos的集合的點,

既然不能表示,就考慮將q拆成兩部分,

一部分是x串集合構成的點,是新開的點,記爲nq,有len[nq]=len[p]+1

另一部分是y串集合構成的點,保持原來的q不變,

 

起先,令nq的所有自動機轉移關係(所有ch節點)等於原先的q,這樣做可行的原因是:

nq是q的後綴,後續在加入新的字符z時,設其跳到狀態nr,

由於nq和q只有碰到字母c時有區別,而z不等於c(設z=c,則nr是第一個包含了np的所有endpos的點,與nq是第一個包含了np的所有endpos集合的點矛盾),

故沒有受到新字母c的影響,所以二者唯一的區別endpos{n},沒有給後續的nr的狀態帶來影響

 

然後考慮fa的關係,nq是舊q的一部分,fa(nq)=fa(q),新q比nq長,可以令fa(q)=nq

構建這個nq節點的意義在於表示np,所以令fa(np)=nq

 

舊q的兒子(轉移關係ch)原封不動保留到了新q和nq中,考慮fa的改變

原先有一些節點指向q,但現在nq成爲了q和np的fa,這些點應該指向nq,

所以應從p往其祖先回跳,若其碰到字母c的轉移爲q,就應將其改爲nq,

對於沒有通過c轉移到q的那些祖先節點,其一定指向了nq的祖先,故不用修改

心得

感覺自己死摳知識點的證明,不做題,沒啥大的意義,

有些知識點感性理解一下就好了……

但總之思路來源的博主寫的很好,

滿足了我這個zz對於原理的一切幻想……

 

去年8月、11月自學過SAM的原理,但是後來沒做題,就漸漸忘了,

現在想想,SA前年當時花了四五天摳原理,但現在也只記得rank、sa、height數組是幹啥的了,

 

實際上,SAM的情況就分三種,代碼也很短,掌握了性質就可以了……

證明什麼的,除了O(n)的點數和邊數的證明略複雜以外,剩下的還好……

沒必要每次做題前都複習一下證明,更何況這玩意比SA做題直接很多……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章