大略:
回文树本质是一棵树,有两个根,奇根下面都是奇回文,偶根下面是偶回文,偶根(fail)指向奇根,到奇根一定适配。
- 求串(S)前缀(0 - i)内本质不同回文串的个数
- 求串(S)内每一个本质不同回文串出现的次数
- 求串(S)内回文串的个数(其实就是(1)和(2)结合起来)
- 求以下标i结尾的回文串的个数
板子:
struct PAM{
int nex[maxn][26]; //指向的一个字符的节点
int fail[maxn]; //失配节点
int len[maxn]; //当前节点回文长度
int str[maxn]; //当前添加的字符串
int cnt[maxn]; //节点出现次数
int last; //目前走到哪个节点
int tot; //PAM中节点数
int N; //添加的串的个数
int newnode(int L){ //新建节点
for(int i = 0; i < 26; i++) nex[tot][i] = 0;
len[tot] = L;
cnt[tot] = 0;
return tot++;
}
void init(){
tot = 0;
newnode(0);
newnode(-1);
last = 0;
N = 0;
str[0] = -1;
fail[0] = 1; //偶根指向奇根,因为到奇根必匹配
}
int getfail(int x){ //失配
while(N - len[x] - 1 < 0 || str[N - len[x] - 1] != str[N]) x = fail[x];
return x;
}
void add(char ss){
int c = ss - 'a';
str[++N] = c;
int cur = getfail(last); //最长可扩增的后缀回文节点
if(!nex[cur][c]){
int now = newnode(len[cur] + 2);
fail[now] = nex[getfail(fail[cur])][c];
//cur后缀(除自己)的最长的能让now失配的后缀
nex[cur][c] = now;
}
last = nex[cur][c];
cnt[last]++;
}
void count(){
for(int i = tot - 1; i >= 0; i--) //子节点出现父节点也出现
cnt[fail[i]] += cnt[i];
}
}pam;