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棵迴文樹(當然一棵用完了初始化再用一次也可以的)。