關於回文樹的理解
前言
這段時間搞字符串上了癮?
看起來是的
那就繼續搞吧
Part1一些名詞
回文串
不想解釋什么意思
回文子串
一個串的子串,它是回文串,那么它就是回文子串
最長回文后綴
對於一個長度小於自己的后綴,如果它是回文串,並且不存在比它更長的回文后綴,那么它就是最長回文后綴
最長回文前綴
基本和上面一樣
Part2 回文樹的形態
長成啥樣啊?
我們很容易知道,回文串有兩種,一種長度是奇數,一種長度是偶數
而在回文樹上走,我們肯定不是一次只在后面添加一個字符
顯然是在前后各添加一個字符
所以我們不難得出一點,如果串可以變成另外一個回文串
那么它的長度一定加上了一個偶數
所以在回文樹上,為了區分這兩種不同的回文串
所以回文樹相當於一個森林
有兩棵樹,一棵的代表長度為奇數的回文串,另一棵代表長度為偶數的回文串
就像后綴自動機,Trie樹,AC自動機這些東西一樣
每一個節點代表的都是一個(些)串
回文樹的每個節點也是代表着一個串
對於每個點的轉移,比如說
對於某個點代表的回文串"aba"
假如它有一個'c'的轉移
那么,"aba"就會指向一個代表着"cabac"的串
同樣的,類似於AC自動機有\(fail\),后綴自動機有\(parent\)
當失配的時候回文樹也有\(fail\)向上跳
那么,我們來考慮一些這個東西是什么?
假設當前加入的位置是\(r\)
如果之前已經匹配出了一個回文\(S_{l..r-1}\)
那么,如果有\(S_{l-1}=S_r\)就沒有失配
如果失配了,因為\(r\)位置是不能變動的
所以挪動的只有\(l\)位置
而\(S_{l..r}\)顯然也要是一個回文串
所以\(l\)挪動到的位置就是\(S_{l..r-1}\)的最長回文后綴的開始位置
一些小小的結論
綜上所述,我們知道了兩點:
1.對於回文樹上的兩個節點,如果存在字符c的連邊,那么,就會從串x,變成cxc
2.對於回文樹上的失配(fail)指針,指向這個點所代表的字符串的最長回文后綴所在的節點
一些小小的證明
接下來,我們還可以知道幾點
1.對於任何一個串S,它的本質不同的回文串的個數不會超過|S|個
2.如果在串S后面加入一個字符,新增的本質不同的回文串的個數不會超過1個
怎么證明?
利用數學歸納法來證明
當\(|S|=1\)時,顯然成立
如果我們知道\(|S|=x-1\)時成立,現在插入\(x\)位置,字符為\(c\)
如果以\(x\)位置結尾出現了兩個新的本質不同的回文串
假設較長的從\(l1\)開始,較短的從\(l2\)開始
因為\(|S_{l1..r}|>|S_{l2..r}|\)
又根據回文串對稱的性質
所以\(S_{l2..r}\)在\(S_{l1..l1+r-l2}\)必定出現過
所以不存在兩個本質不同的回文串
所以最多新增一個本質不同的回文串
所以到\(x\)位置出現的本質不同的回文串的個數最多為\(x\)個
同時,我們也證明了每次插入一個新的字符,最多增加一個本質不同的回文子串
Part3 回文樹的構造
看完了上面,應該就知道了回文樹上的東西代表着什么
我們的構造采用增量法,也就是類似於后綴自動機的\(extend\)
假設前面已經構造出了\(1..x-1\)的回文樹,現在要加入第\(x\)個字符\(c\)
因為要接在\(x-1\)的后面,我們又知道最多一個產生一個新的本質不同的回文串
也就是\(S_{1..x-1}\)中,最長的某個回文后綴\(S_{l..x-1}\),
同時能夠滿足\(S_{l-1}=S_x\)
因為只需要不停地尋找最長回文后綴
根據回文樹上的\(fail\)指針的含義
我們很容易知道知道,
只需要從上一個位置添加完之后的最后一個位置
(也就是以\(x-1\)為結束位置的最長回文子串)
所代表的節點開始,沿着\(fail\)一路上跳
檢查是否滿足\(S_{l-1}=S_x\)就行了
假設這樣找到的一個位置是\(p\)
不難證明這個位置\(p\)一定存在(為啥?長度為1的回文串呀)
如果\(p.son[c]\)也就是連邊\(c\)已經存在
那就什么都不用干,因為這個回文子串已經存在過
不需要重新建邊
否則,重新建一個點表示這個回文子串,假設點是\(np\)吧
然后\(p.son[c]=np\)
現在我們要找\(np\)的\(fail\)啦
因為要找的是最長回文后綴,不能是自己
所以令\(k=p.fail\)
然后就像前面一樣的,找到第一個滿足\(S[l_k-1]=S[n]\)的點
讓\(k\)沿着\(fail\)向上跳
然后\(np.fail=k\),表示找到啦
這樣,我們的回文樹就利用增量法構建出來啦
當然,兩棵樹的根節點的長度分別是\(-1\)和\(0\)
然后為\(0\)的根節點的\(fail\)連向\(-1\)的根節點
\(-1\)個根節點的\(fail\)也連向自己
為啥?自己想
初始情況下的\(last=0,tot=1\)(代表什么可以參考程序)
這是一棵回文樹
struct Palindromic_Tree
{
struct Node
{
int son[26];
int ff,len;
}t[MAX];
int last,tot;
void init()
{
t[++tot].len=-1;
t[0].ff=t[1].ff=1;
}
void extend(int c,int n)
{
int p=last;
while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
if(!t[p].son[c])
{
int v=++tot,k=t[p].ff;
t[v].len=t[p].len+2;
while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
t[v].ff=t[k].son[c];
t[p].son[c]=v;
}
last=t[p].son[c];
size[t[p].son[c]]++;
}
};
Part4 后記
這篇博客十分簡短
因此肯定有很多很多不嚴謹的地方
更加詳細的請參考
國家集訓隊\(2017\)年的論文
當然了,這些東西也只是我自己的理解
而回文樹也有很多很神奇的用法,
等我做了一些題之后我會再回來寫的。