manacher算法是什么?
这是一种求最长回文字串的算法。
首先要采用一种方法,向原串中每隔一个字符加入一个没有在原串中出现过的字符,这样能够避免分类讨论奇数长度的回文串和偶数长度的回文串。同时再在头部和尾部加入两个不相同的字符,防止寻找时溢出字符串范围
例如:
abba
这个串如果向中间加入
$
,头部加入
!
,尾部加入
考虑暴力求法:枚举每一个点,向左和右分别寻找,如果两个寻找到的字符相等,那么以这个点为中心的回文串长度加2,时间复杂度
O(n2)
。
在下面这个表中,@代表匹配的中心,=代表向外扩充的位置。
字符 | ! | a | b | b | a | ∗ | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
第一次匹配 | @ | ||||||||||
第二次匹配 | @ | ||||||||||
第三次匹配 | = | @ | = | ||||||||
第四次匹配 | @ | ||||||||||
第五次匹配 | = | @ | = | ||||||||
第六次匹配 | = | = | = | = | @ | = | = | = | = | ||
第七次匹配 | = | @ | = | ||||||||
第八次匹配 | @ | ||||||||||
第九次匹配 | = | @ | = | ||||||||
第十次匹配 | @ | ||||||||||
第十一次匹配 | @ |
可以看出,上面有些地方是被重复统计很多次的,可以利用回文串的对称性来进行优化。也就是说,如果一个串
记录一个数组
pi
代表以
i
为中心的最大回文半径,那么显然可以给出下面这个伪代码:
int id,rmax,p[maxn+10],ans;
//id代表能使以该点为回文中心的回文串末尾位置最大的点
//rmax代表以id为回文中心的最长回文串的末尾坐标
//ans记录答案
init();
id=rmax=p[1]=1;
for(i=2->len)
{
if(i in 1..rmax)
{
p[i]=min(p[i与id对称的点],i与rmax的距离);//因为范围不能超过回文串的长度
}
else
{
p[i]=1;
}
while(找到的串长度还可以延伸)
{
++p[i];
}
if(可以更新id和rmax)
{
更新id和rmax;
}
ans=max(ans,p[i]-1);
}
那么按照这个算法匹配的结果是怎样的呢?
在下面这个表中,@代表匹配的中心,=代表向外扩充的位置,-代表由以前的串而可以知道有回文的位置。
字符 | $ | a | b | b | a | ∗ | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
第一次匹配 | @ | ||||||||||
第二次匹配 | @ | ||||||||||
第三次匹配 | = | @ | = | ||||||||
第四次匹配 | @ | ||||||||||
第五次匹配 | = | @ | = | ||||||||
第六次匹配 | = | = | = | = | @ | = | = | = | = | ||
第七次匹配 | - | @ | - | ||||||||
第八次匹配 | @ | ||||||||||
第九次匹配 | - | @ | - | ||||||||
第十次匹配 | @ | ||||||||||
第十一次匹配 | @ |
可以看出,虽然降低的次数不是很多,但是确实降低了。
复杂度?
代码
#include <cstdio>
#include <cstring>
#include <iostream>
const int maxn=1000000;
char s[maxn+10],a[(maxn<<1)+10];
int len,p[(maxn<<1)+10],id,rmax,ans,c;
inline int solve()
{
++c;
a[0]='!';
a[1]='#';
for(register int i=1; i<=len; ++i)
{
a[i<<1]=s[i];
a[i<<1|1]='#';
}
len=len<<1|1;
a[len+1]='&';
memset(p,0,sizeof p);
id=1;
rmax=1;
p[1]=1;
for(register int i=2; i<=len; ++i)
{
if(i>rmax)
{
p[i]=1;
}
else
{
if(p[(id<<1)-i]<rmax-i)
{
p[i]=p[(id<<1)-i];
}
else
{
p[i]=rmax-i;
}
}
while(a[i+p[i]]==a[i-p[i]])
{
++p[i];
}
if(rmax<i+p[i]-1)
{
rmax=i+p[i]-1;
id=i;
}
}
ans=0;
for(register int i=1; i<=len; i++)
{
if(p[i]-1>ans)
{
ans=p[i]-1;
}
}
printf("Case %d: %d\n",c,ans);
return 0;
}
int main()
{
while(1)
{
scanf("%s",s+1);
len=strlen(s+1);
if((len==3)&&(s[1]=='E')&&(s[2]=='N')&&(s[3]='D'))
{
break;
}
solve();
}
return 0;
}