1. 程式人生 > 實用技巧 >迴文樹

迴文樹

迴文樹可以用來處理一個字串中所有的迴文子串。

一個字串的迴文樹由兩棵樹組成,一個維護所有長度為奇數的迴文子串,一個維護所有長度為偶數的迴文子串。樹上除根節點外的每個節點都表示串中的一個迴文子串。

\(len:\) 節點對應的迴文子串長度。

\(fail:\) 指向該節點所對應的迴文子串的最長迴文字尾所對應的節點。

\(ch:\) 轉移到該樹的另一個節點,轉移為向當前回文子串兩端加上一個字元。

為方便處理,偶樹的根的 \(len\) 設為 \(0\)\(fail\) 設為奇樹的根,奇樹的根的 \(len\) 設為 \(-1\)\(fail\) 設為其本身。

構造時用增量法即可。

\(code:\)

void init()
{
    len[1]=-1,fail[0]=fail[1]=tot=1;
}
void insert(int i)
{
    int p=las,c=s[i]-'a';
    while(s[i-1-len[p]]!=s[i]) p=fail[p];
    if(ch[p][c])
    {
        las=ch[p][c];
        return;
    }
    int x=fail[p],y=++tot;
    while(s[i-1-len[x]]!=s[i]) x=fail[x];
    fail[y]=ch[x][c],len[y]=len[p]+2,ch[p][c]=las=y,cnt[y]=cnt[fail[y]]+1;
}

\(fail\) 樹:

\(code:\)

for(int i=0;i<=tot;++i)
    if(i!=1)
    	add(fail[i],i);

一個字串的本質不同迴文子串個數即為其迴文樹除了兩個根的節點個數。

字串中一個位置的迴文字尾個數即為該位置對應的節點的 \(fail\) 鏈長度。

在維護每個本質不同迴文子串的出現次數時,還需在 \(fail\) 樹上用兒子來更新父親。

\(code:\)

for(int i=tot;i;--i) cnt[fail[i]]+=cnt[i];

有時還需用到 \(trans\),指向長度小於等於其迴文子串長度一半的最長迴文字尾的節點,建樹時維護即可。

\(code:\)

void insert(int i)
{
    int p=las,c=s[i]-'a';
    while(s[i-1-len[p]]!=s[i]) p=fail[p];
    if(ch[p][c])
    {
        las=ch[p][c];
        return;
    }
    int x=fail[p],y=++tot;
    while(s[i-1-len[x]]!=s[i]) x=fail[x];
    fail[y]=ch[x][c],len[y]=len[p]+2,ch[p][c]=las=y;
    if(len[y]<=2) trans[y]=fail[y];
    else
    {
        int q=trans[p];
        while(s[i-1-len[q]]!=s[i]||(len[q]+2)*2>len[y]) q=fail[q];
        trans[y]=ch[q][c];
    }
}