作用
好像是2015年时战斗民族某巨佬发明的,可以快速求一个串里有多少本质不同的回文子串以及每个回文子串出现的次数。
实现
回文自动机由两棵树构成,一棵是 even 树,另一棵是 odd 树。每个节点对应了一个回文子串,有如下信息:
- len :该回文子串的长度。
- fail :该回文子串最长回文后缀对应的节点。
- son[k] :在该回文字串两端添加 k 字符之后到达的节点。
很像AC自动机啊……特别的,
回文自动机和后缀自动机一样,每次是从上一状态拓展过来的,所以需要记录当前节点
p
(刚开始
找到
sonp′[si]
之后我们需要构造
sonp′[si]
的
fail
(如果
sonp′[si]
原先不存在的话),其实很简单,只需要再找一次
failp′
在
fail
树上的最近祖先
k
,让
效率
因为回文子串最多
|s|
个,所以
p
“下降”或“上升”最多
模板
BZOJ3676,另一种方法是后缀数组+Manacher……你们自己体会一下。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=300000,maxi=26;
int n,num[maxn+5];char s[maxn+5];LL ans;
int si,p,son[maxn+5][maxi],fai[maxn+5],len[maxn+5];
inline void Extend(char *s,int i){
while (s[i]!=s[i-len[p]-1]) p=fai[p];
if (!son[p][s[i]-'a']){
si++;int k=fai[p];len[si]=len[p]+2;
while (s[i]!=s[i-len[k]-1]) k=fai[k];
fai[si]=son[k][s[i]-'a'];son[p][s[i]-'a']=si;
}
p=son[p][s[i]-'a'];num[p]++;
}
int main(){
freopen("PAM.in","r",stdin);
freopen("PAM.out","w",stdout);
scanf("%s",s+1);n=strlen(s+1);
si=1;p=0;fai[0]=fai[1]=1;len[0]=0;len[1]=-1;
for (int i=1;i<=n;i++) Extend(s,i);
for (int i=si;i>1;i--) ans=max(ans,(LL)num[i]*len[i]),num[fai[i]]+=num[i];
return printf("%lld\n",ans),0;
}