两个处理回文串的利器。
马拉车可以求出每个字符为中心的最长回文串。
回文树可以统计本质不同的回文串数量以及每个回文串的个数,以下标i结尾的回文串个数。
马拉车
原理
首先在第一个位置插入一个’$’防止溢出,然后再所有字符之间插入’#’来处理奇偶回文串的问题。
大概原理就是如过当前回文串最右端包括了当前区间,那么就可以通过对称点得到一个初始值,如果不在初始值就为1。
然后暴力匹配即可。
最终算出的P数组代表以某个字符串为中心的半径,回文串的长度就是pi-1
时间啊复杂制度是O(n)的,因为每次暴力匹配只会匹配从未匹配的点。
代码
void manacher(char *s,int n)
{
int mx=0,id=0;
for(int i=1;i<=n;i++)
{
p[i]=(mx>i)?min(p[2*id-i],mx-i):1;
while(s[i-p[i]]==s[i+p[i]])p[i]++;
if(i+p[i]>mx)
{
mx=p[i]+i;
id=i;
}
}
}
mx是开区间,这样比较好写。
回文树
有点像AC自动机
#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=300000+105;
char s[maxn];
struct PalindromicTree//回文树
{
int np,last,fail[maxn],cnt[maxn],len[maxn],ch[maxn][30];
#define getId(c) (c)-'a'
void Initial()
{
np=-1;//为了利用根是0的这个方便性质
memset(ch,0,sizeof(ch));
memset(cnt,0,sizeof(cnt));
memset(len,0,sizeof(len));
memset(fail,0,sizeof(fail));
}
int Newnode(int x)
{
len[++np]=x;
return np;
}
int getFail(int x,int i)
{
while(s[i-len[x]-1]!=s[i])x=fail[x];//想要延伸匹配的话就是s[i-len[x]-1]以及s[i]
return x;
}
void Build()
{
int cur,id,now;
np=-1;
s[0]=-1;//这里是为了防止溢出
fail[0]=1;last=0;//初始化指针
Newnode(0);Newnode(-1);//不能交换顺序,和后面的ch为0有些关系,加入-1的结点可以帮助判断奇串
for(int i=1;s[i];i++)
{
id=getId(s[i]);
cur=getFail(last,i);
if(!ch[cur][id])//建立新结点,跟新fail,当前点的ch
{
now=Newnode(len[cur]+2);
fail[now]=ch[getFail(fail[cur],i)][id];//在没有对应回文串的时候我们指向0这个结点重新匹配
ch[cur][id]=now;//顺序不能换
}
cnt[last=ch[cur][id]]++;//更新last,cnt
}
for(int i=np;i>=0;i--)//这里累加统计一个回文串到底有多少个元素
cnt[fail[i]]+=cnt[i];
}
void query()
{
LL ans=0;
for(int i=0;i<=np;i++)
ans=max(ans,1ll*len[i]*cnt[i]);
printf("%lld\n",ans);
}
}PAM;//PAM即是回文自动机
int main()
{
freopen("in.txt","r",stdin);
scanf("%s",s+1);
PAM.Build();
PAM.query();
return 0;
}