迴文樹筆記(轉自quack_quack)

1.迴文樹的next[charset]指針:
b->aba
那麼就這樣表示:b.next[a]=aba
當然樹裏面肯定不能存字符串,於是就直接用下標標號代替了
2.迴文樹的fail指針:
跟ac自動機類似,fail指針指向當前節點的最大回文後綴
沒有就指向根
3.迴文樹的根
有2個根,一個單根就是往下連回文串長度爲奇數的節點,本身長度爲-1
還有個雙根就是往下連回文串長度爲偶數的節點,本身長度爲0
雙根的fail指向單根
當然,也可以像manacher那樣,aab->&a&a&b&,這樣只用單根就是一棵樹了。
可以樹DP或者可持久化什麼的。。。
4.迴文樹節點的域
len->表示當前節點回文串的長度,一般做迴文串長度的題會用
cnt->表示當前節點被插入過多少次,一般做計數類的題會用,一般需要配合count函數
代碼模板:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=100005;
const int N=26;
struct Palindromic_Tree{
    int next[MAXN][N],fail[MAXN],cnt[MAXN],len[MAXN],S[MAXN],last,n,p;
    int newnode(int l)
    {
        for(int i=0;i<N;++i)next[p][i]=0;
        cnt[p]=0;
        len[p]=l;
        return p++;
    }
    void init()
    {
        p=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getfail(int x)
    {
        while(S[n-len[x]-1]!=S[n])x=fail[x];
        return x;
    }
    void insert(int c)
    {
        c-='a';
        S[++n]=c;
        int cur=getfail(last);
        if(!next[cur][c])
        {
            int now=newnode(len[cur]+2);
            fail[now]=next[getfail(fail[cur])][c];
            next[cur][c]=now;
        }
        last=next[cur][c];
        ++cnt[last];
    }
    void count()
    {
        for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i];
    }
}pt;
char s[MAXN];
int main()
{
    scanf("%s",s);
    int n=strlen(s);
    pt.init();
    for(int i=0;i<n;i++)
        pt.insert(s[i]);
    pt.count();
    for(int i=0;i<pt.p;++i)
        printf("%d\n",pt.cnt[i]);
}

下面說說用這個模板怎麼做迴文樹的題:
1.最長迴文子串

for(int i=0;i<pt.p;++i)
    ans=max(ans,pt.len[i]);

很簡單吧,如果要輸出的話還要保存插入進去的字符的下標。
但是一般用manacher寫這個題會更簡單。
2.求字符串中有多少個本質不同的迴文子串
例如abad,本質不同的迴文字串有:a,b,d,aba,共4個。

ans=pt.p-2;

很簡單吧,其實就是看看回文樹裏面除了根以外有幾個節點。
3.對於一個空字符串s,每次在s末尾加上一個字符,對於每次操作,求字符串中有多少個本質不同的迴文子串
其實跟上面代碼一樣。。每插入一次就算一次ans。提出這個只是爲了說明迴文樹是動態的。當然,如果這個s支持刪除操作那麼應該會用到可持久化迴文樹。刪除就得回到之前的版本。
4.求字符串每個迴文子串出現過多少次
例如abad,本質不同的迴文字串有:a,b,d,aba,共4個。
a出現過2次。
b出現過1次。
d出現過1次。
aba出現過1次。
終於用到cnt數組了。
上面那個模板其實就是解決這個問題的。
就是非常簡單的樹DP

void count()
{
    for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i];
}

因爲i這個節點的fail[i]就是i的最長迴文後綴
假設i出現過cnt[i]次,那麼fail[i]除了它自己出現cnt[fail[i]]次,還在i中出現了cnt[i]次。並且還要倒着從葉子節點往根推。
最後要輸出答案的話,一般還是要保存下標
根據下標和迴文串長度來按題意順序輸出cnt
5.求字符串中迴文串的個數
例如abad。a出現過2次。b出現過1次。d出現過1次。aba出現過1次。
因此迴文串有5個。
可以發現的是,剛纔的樹DP已經推到根了,那麼根的cnt值就是答案。因爲兩個根之間有fail關係所以輸出哪個根的cnt都可以。
6.求兩個字符串的公共迴文串的個數(2014-2015 ACM-ICPC, Asia Xian Regional Contest)
建兩棵迴文樹,分別insert兩個字符串。
然後分別從2個根開始沿着next數組下去dfs。
如果treea.next[cura][i] 和treeb.next[curb][i] 都存在
那麼說明兩個字符串都有相同的迴文串,於是

cura=treea.next[cura][i];
curb=treeb.next[curb][i];
ans+=treea.cnt[cura]*treeb.cnt[curb];

然後遞歸下一層dfs(cura,curb);。
如果treea.next[cura][i] 和treeb.next[curb][i] 有一個不存在就不往下dfs了。
7.BZOJ 2565 最長雙迴文串
給出一個字符串,求所有子串中能分成前後兩個部分都是迴文串最長的子串的長度。
問題實際上是求兩個這樣的數組left[],right[],分別表示以某位置結尾往左或往右最長的迴文子串。
這個怎麼搞?
就是給出一個空字符串s,每次往s的末尾加一個字符,然後查詢s裏面包含末尾字符的最長的迴文子串的長度。修改一下insert函數:

int insert(int c)
{
    c-='a';
    S[++n]=c;
    int cur=getfail(last);
    if(!next[cur][c])
    {
        int now=newnode(len[cur]+2);
        fail[now]=next[getfail(fail[cur])][c];
        next[cur][c]=now;
    }
    last=next[cur][c];
    ++cnt[last];
    return len[last];
}

insert函數的返回值就是要求的。
如果從左往右加字符,得到的結果就是left[],如果從右往左加字符,得到的就是right[]。然後枚舉中間點,答案是left[i]+right[i],就可以找最大值。
所以需要2棵迴文樹(當然一棵用完了初始化再用一次也可以的)。

發佈了116 篇原創文章 · 獲贊 24 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章