1. 程式人生 > >回文自動機學習筆記

回文自動機學習筆記

端點 一個表 關心 () ots 2.0 最大的 .com --

回文自動機學習筆記

這兩天學習了回文自動機,於是在此總結一下,順便復習一下所需要的預備知識。

1 【回文串基礎】

1.1

回文串定義:長度為n,下標從0開始的字符串s是回文串,滿足\(\forall i\in \mathbb{N},s[i]=s[n-1-i]\)

1.2

\(Manacher\)算法

這裏用歸納法的方式簡介地介紹這種算法:

\(h[i]\)表示以字符串第i個位置為中心的回文串長度。

假設已經計算出\(h[0],h[1]\dots h[i-1]\)的值。

可以由此計算出最大的回文串的右端點位置\(p\), 和右端點\(p\)對應回文中心\(pos\) 。(實際上可以直接繼承)

如果\(p \ge i\)

, 則說明在\(pos\)為中心的回文串中有一個i的鏡像位置。

  • 如果鏡像位置的邊界和\(pos\)對應回文串的邊界不重疊,那麽位置\(h[i]\)的長度不會超過鏡像位置的長度。

  • 如果邊界重疊,則\(h[0],h[1]\dots h[i]\)的pos和p可能被i刷新。

如果\(p < i\), 則說明在i可以刷新\(pos\)\(p\)

\(p\)\(pos\)不能被i刷新的時候,我們就可以從鏡像位置繼承\(h[i]\)的值

\(p\)\(pos\)可以被i刷新的時候,我們就可以同時推算出\(h[i]\)的值。

這裏p每次被刷新,只會不斷增大。所以可以證明算法的復雜度是和字符串長度呈線性關系的。

1.3

本質相同的回文子串 定義:如果兩個子串長度相同,每個位置的元素相同,則是本質相同的回文串。

一個字符串裏面,最多會有多少本質不同的回文子串?

在做\(Manacher\)的時候,只有拓展p的時候,可能產生新的回文串。(其余的回文子串一定在鏡像位置出現過),所以答案和字符串長度線性相關。

2 【回文自動機】

2.0

自動機定義自行百度

2.1

結構:

自動機中每一個點表示一種回文串,且與自動機中的其他點表示的回文串本質不同。

一個回文串可以通過在兩邊添加各一個字符,變成另外一個回文串,則這兩個回文串代表的點之間有一條有向邊,方向從短的串指向長的串。

每一個點的fail指針指向它最長的回文後綴。

每一個點上記錄回文串的長度,出現次數。

有向邊組成了兩顆樹形圖,一個表示奇數串,一個表示偶數串。

初始狀態有兩顆樹的根節點分別表示一個長度-1的串和一個空串。

2.2

構造:

假設已經構造好了第1到i-1字符的的回文自動機,並記錄右端點在i-1位置上的最長回文後綴節點,考慮添加第i個字符對它的影響。

由於我們已經構造好1到i-1所有的後綴,所有新產生的回文串一定是在位置i結尾的回文後綴。順著i-1位置的回文後綴的fail指針往回跳,路途經過的回文後綴中滿足左端點和右端點字符相同的回文串都是i結尾的回文串。

實際上我們只需要第一個左右端點相同的後綴打一個累加標記;如果這個位置是新的回文子串,則需要找到下一個回文後綴,並連上fail指針。而且根據回文串的對稱性,不需要繼續找下去了。

最後記得按拓撲序把標記依次下傳就行了。

2.3

構造回文自動機的時間復雜度

  1. 根據1.3的結論,回文自動機總點數不超過原串字符串長度。
  2. 構造的時間復雜度,實際上和構造時尋找合適的回文後綴時“左右端點的移動次數+ 更新fail指針的移動次數”成正比。

根據第二點,我們只需要關心這兩個東西就好了。

  1. 右端點移動次數:顯然右端點單調向右,移動次數N。
  2. 右端點每移動一次,左端點向左移動一次,所以左端點頂多向右移動2N次,向左移動N次。
  3. fail指針移動次數和第2點分析方式類似。

總而言之,時間復雜度O(n)

//博主老了,不想寫了,就這樣吧(生無可戀(灬°ω°灬)

2.4

模板

int s[N], ch[N][26], fail[N], len[N], cnt[N], last, ecnt, n;
void init() {
    fail[0] = 1; fail[1] = 1; 
    len[0] = 0;  len[1] = -1;
    ecnt = 1; last = 1;
}
void extend( int w ) {
    int p = last; s[++n] = w;
    while( s[n-1-len[p]] != w ) 
        p = fail[p];
    if(!ch[p][w]) {
        int u = ++ecnt, v = fail[p];
        while( s[n-1-len[v]] != w ) v = fail[v];
        len[u] = len[p]+2; fail[u] = ch[v][w]; ch[p][w] = u;
    }
    last = ch[p][w]; cnt[last]++;
}

void calc() {
    for( int i = ecnt; ~i; i -- ) cnt[fail[i]] += cnt[i];
}

3 參考資料

CSDN上的一篇比較詳細的博客

跟黃學長的版對拍了一下下

還有一篇,看過但是翻不到了。。。QAQ

4 吐槽

博主原來是想把這個東西講得很細的,結果寫到一半沒了幹勁(補番去了

如果想要細節去看其他博主的博客吧o(╥﹏╥)o

當然,如果文章裏面有錯誤,但願讀者慷慨指出,多謝!

回文自動機學習筆記