【[APIO2014]回文串】回文自动机moban
与Manacher+后缀自动机倍增匹配相比快太多。
以上是回文自动机的速度

以上是Manacher+SAM的速度。
感受一下。。(受个人LJ代码的常数影响)
回文自动机是一个类字典树,构造方式又类似于AC自动机。其每个结点就代表了一个回文串(这与后缀自动机一个结点代表多个字符串不同),由于有单数长度回文串后偶数长度回文串的影响,所以我们这个树有两个根分别代表的长度为0和-1,但最后所以无法回跳时的fail 都指向长度为0的结点。在此后堆每个结点对于他的父亲都长度+=2
回跳指针代表的意义是结点后缀中长度最长的那一个回文串(最后一个位置的字符相同)。在我们构造的时候,加入一个字符t考虑以上一个到达的结点(字符串中的上一个)las跑fail,找到第一个能够容下t成为回文的位置(也就是找到一个回文 结点,其前面那个字符为t)。然后如果这个位置cur没有 nt[cnr][t]那么我们又找到了一个本质不同的回文。
加入新结点的回跳指针就很像AC自动机 ,在他找到的结点cur这里的fail[cur]里再跑fail找到一个结点能够与t匹配。我们显而易见的发现他长度变短,但最后一个字符为t。然后将fail[新结点]设置为nt[fail[cur]找到的结点][t]。
最后的cnt可见对于一个结点x其fail[x]一定为其子回文串,在构造完后倒序往前加一遍即可。
极弱的code:
#include<stdio.h> #include<bits/stdc++.h> using namespace std; const int maxn = 300005; int tot,pos,las,nt[maxn][27],fail[maxn]; int s[maxn],len[maxn],cnt[maxn],fa[maxn]; int push(int ll) { len[++tot]=ll; return tot; } void init() { tot = las = 2; len[1]=-1; len[2]=0; fail[0] = fail[1] = fail[2] = 1; s[0]=-1; } int getfail(int x) { while(s[pos]!=s[pos-len[x]-1]) x=fail[x]; return x; } void extend(int t) { s[++pos] = t; int cur = getfail(las); if(!nt[cur][t]) { int now = push(len[cur]+2); fail[now] =nt[ getfail(fail[cur]) ][t]; nt[cur][t] = now ; //num[now]=num[cur]+1这里也可以记录回文链的长度 } las = nt[cur][t]; cnt[las]++; } char ss[maxn]; int main() { init(); scanf("%s",ss+1); int n=strlen(ss+1); for(int i=1;i<=n;i++) extend(ss[i]-'a'); long long ans=0; for(int i=tot;i>=1;i--) cnt[fail[i]]+=cnt[i]; for(int i=1;i<=tot;i++) ans = max(ans,1LL*len[i]*cnt[i]); printf("%lld",ans); }