【[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);
}

浙公网安备 33010602011771号