後綴自動機(SAM)速成手冊!

正好寫這個博客和個人某個別的需求重合了。。。我就來說一講SAM啦qwqhtml

後綴自動機,也就是SAM,是一種極其有用的處理字符串的數據結構,能夠用於處理幾乎任何有關於子串的問題,但以學起來異常困難著稱(在機房裏,最早學會SAM的永遠是大佬(好比litble和zyf(他在退役前就學了)))。算法

可是!!!當你學了SAM並熟練地刷了幾道題後,你會發現——你以前爲了學SAM而強行理解的許多定理,對你應用SAM一點用處也沒有!爲了引出構造算法,幾乎全部博客都會詳細地解釋「你爲啥要這樣作」,然鵝。。。數組

<font face="黑體" color="#ff00ff" size=5> SAM徹底能夠當成黑盒來用!!!!!! </font>數據結構

因此我打算寫一篇SAM速成博客。。。保證即學即會!學習

在構建以前你不得不知道的

<font face="黑體" color="#00a0ff" size=5>Warning:想完全理解後綴自動機嗎?那你有好消息了!請當即關閉此頁面,在百度裏搜索「後綴自動機 陳立傑」,開始愉快的學習吧!</font>(講真,陳老師的ppt是講的最好的,別的博客無能出其右者)spa

SAM是一個DAG(有向無環圖),每一個點表明一個**"狀態"**,邊表明狀態轉移,邊上有一個字母。SAM有一個起始狀態(稱爲起點),從起點開始,沿着邊不斷走下去,就能夠獲得一個字符串。記當前停留節點爲$x$,走出來的字符串爲$S$,稱節點$x$可表明字符串$S$。記$x$可表明的串中長度最長的串的長度爲$len(x)$。指針

另外,除起點外的每一個節點還擁有一個**「後綴連接「**,記做$fa(x)$。後綴連接組成了一棵樹,別的性質在構建完以後再講。code

存儲SAM利用的是相似於Trie樹的存儲結構,即便用$ch[][26]$數組保存狀態轉移的邊。htm

知道了這些,構建SAM的工做就能夠開始了。blog

開始建造後綴自動機

準備工做:創建數組$ch,fa,len$,準備指針$last,cnt$。SAM的構造方法是不斷地向已經建好的SAM中加入新的節點。$last$表示上一個被插入的節點,$cnt$表示SAM中的節點數量。一開始,$last=cnt=1$,表示只有一個起點的初始SAM。

接下來,假設要往SAM里加入一個字符$x$。

  1. 新建節點$np=++cnt$。新建節點$p$。$p=last$。$ last=np$。
  2. 若是不存在$ch[p][x]$,令$ch[p][x]=np,p=fa[p]$。重複此步驟。
  3. 若是到最後尚未一個$p$擁有兒子$x$,令$fa[np]=1$。退出過程。
  4. 當$ch[p][x]​$出現時,令$q=ch[p][x]​$。若是$len[q]==len[p]+1​$,令$fa[np]=q​$。退出過程。
  5. 不然有點麻煩。新建節點$nq=++cnt$,將$q$的兒子都複製給$nq$,令$len[nq]=len[p]+1$。
  6. 令$fa[nq]=fa[q],fa[q]=fa[np]=nq$。
  7. 從$p$開始沿着後綴連接,將全部$ch[p][x]==q$的節點的$ch[p][x]$都替換成$nq$。

將你的字符串的全部字符都一一進行如上操做後,你就獲得了用你的字符串構建出來的SAM。

你不須要知道爲何這麼操做能夠獲得SAM,你只須要記下如下的代碼,作幾道題強化記憶,而後就能夠用SAM的性質來秒題了。

void insert(int x)
{
    int np=++cnt,p=last;
    len[np]=len[p]+1,last=np;
    while(p&&!ch[p][x])ch[p][x]=np,p=fa[p];
    if(!p)fa[np]=1;
    else
    {
        int q=ch[p][x];
        if(len[q]==len[p]+1)fa[np]=q;
        else
        {
            int nq=++cnt;len[nq]=len[p]+1;
            memmove(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q],fa[np]=fa[q]=nq;
            while(ch[p][x]==q)ch[p][x]=nq,p=fa[p];
        }
    }
}

後綴自動機的奇妙性質

如今,你已經擁有SAM了,你須要知道它有什麼用。這裏列舉了SAM的一些基本且經常使用的性質。

<font size=5 color=#7777ff>請牢記如下每一條內容!都十分有用!不要去問「爲何是這樣的」!(若是必定要問,請參照上文藍色放大的Warning)</font>

首先,SAM的點數與邊數都是$O(n)$的。<font color=#00cc00>記住,因爲每次插入最多新建兩個點,因此應該開字符總量兩倍的空間。</font>計算空間時別忘了你開了26倍的$ch$數組。

在SAM上從起點開始沿着邊隨便走走,獲得的必定是子串。同時,每個子串均可以在SAM上走出一條惟一對應的路徑。也就是說,子串和SAM上從起點開始的每一條路徑一一對應。路徑數等於子串數。

起點能夠看作是表明空串的點。

重點:定義子串的$right$集合:這個子串在原串中全部出現的位置的右端點的集合。

好比說:<font color=#00aaff>AA</font><font color=#ff0000>AAB</font><font color=#00aaff>BAAA</font><font color=#ff0000>AAB</font><font color=#00aaFF>A</font><font color=#ff0000>AAB</font><font color=#00aaff>BAA</font>

子串<font color=#ff0000>AAB</font>出現了3次,右端點集合爲${5,12,16}$。這就是子串<font color=#ff0000>AAB</font>的$right$集合。

一個節點可以表明的全部子串的$right$集合是同樣的。$right$集合相等的子串必定被同一個節點表明。(因此,咱們會使用「節點的$right$集合」這個說法。)兩個節點的$right$集合之間要麼真包含,要麼沒有交集。若節點$y$的$right$集合包含了節點$x$的$right$集合,那麼$y$能表明的子串均爲$x$能表明的子串的真後綴。

重點:定義節點$x$的後綴連接$fa(x)$:若是有一些節點的$right$集合包含了$x$的$right$集合,$fa(x)$是其中$right$集合的大小最小的那一個。

後綴連接們組成了一棵「後綴連接樹」(不是後綴樹)。後綴連接樹的根爲起點。若節點$y$的$right$集合包含了節點$x$的$right$集合,那麼$y$在後綴連接樹上是$x$的祖先。

一個節點的$right$集合等於他在後綴連接樹上的全部兒子的$right$集合的並集。並且兒子的$right$集合之間兩兩沒有交集。

每一個節點能表明的子串的長度範圍是一段連續的區間。這很好理解,由於它們的結束位置都是相同的。

咱們求出每一個節點能表明的最長串的長度(即$len(x)$)了,那最短長度呢?其實就等於後綴父親節點的$len+1$。也就是說,全部本質不一樣的子串的數量等於$\sum len(x)-len(fa(x))$。

總結

以上就是SAM的基本性質~對於一道特定的題,你可能須要經過上面的性質推出你須要的新性質。若是你還有什麼疑問能夠向我留言,我(在退役前)會在一天以內回覆的!(你也能夠去問更強的boshi和litble,別去問zyf由於他已經退役了。)

題單我就不給了,由於網上有不少不少。。。

固然,若是你立志要當大佬。。。那趕忙打開陳立傑的ppt吧=。=

感謝您的觀看qwq!

原文出處:https://www.cnblogs.com/sclbgw7/p/10197629.html

相關文章
相關標籤/搜索