【[APIO2014]回文串】回文自动机moban

与Manacher+后缀自动机倍增匹配相比快太多。

manacher+SAM写法

以上是回文自动机的速度

以上是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);
}

 

posted @ 2018-06-05 22:51  Newuser233  阅读(10)  评论(0)    收藏  举报