Manachar是一个求最长回文子串的算法,时间复杂度O(n)。
第一步,扩充原字符串:
我们在每个字符的两旁分别加上一个不曾出现的符号(如#),这样做虽然增长了字符串的长度(记得开两倍空间),但使字符串变成了奇数长度的字符串,便于统一地进行操作。而且原字符串的回文性质也没有改变。
第二步,继承前面的状态:
这步是利用了回文串左右对称的性质。在一个大回文串中,中心为id,右边有一点i,左边有一点i',它们关于id对称。如果我们求得了i'的最长回文子串,i的最长回文子串也可以直接求得。
我们刚刚说的是上面那种情况,是完全在mx里面的情况。
中间的是部分超出的情况。由于i’的回文子串超出了mx',对应的,i的也应该超出mx。但是对于mx外的世界是未知的,我们不能贸贸然地认为超出这一段一定与内部这段对称,所以最多只能扩展到mx,外面的暂且不要贪心地收下。
对于下面的情况就完全没法继承了,i已经完全超出了mx,只能交由下一步(暴力)来处理。
第三步,暴力扩展回文串:
放下的部分我们直接暴力比对,注意要控制边界。
怎么样的子串需要暴力扩展呢?是那些一定程度上超出了mx的子串,如图中、下两种情况。
第四步,更新最远边界:
记下mx(目前最远边界)和id(其中心),mx的意义是我们目前最远暴力比对到的地方。
第四步,计算实际回文串长度:
因为其中存在了无意义的符号,我们要除去其干扰来求有意义的部分长。具体看代码,画画图自己也是可以总结归纳出来的。
例题(poj 3974)
题意 求最长回文串。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxl=1000010;
char ss[maxl];int len;
char s[2*maxl];int nlen;//记录新的字符串 \ 两倍空间
int f[2*maxl];//f[i]表示以i为中心往两边最大扩展多少仍是回文串,即[i-f[i],i+f[i]]是个回文串 /
int main()
{
int T=0;
while(scanf("%s",ss+1),ss[1]!='E')
{
len=strlen(ss+1);nlen=0;
for(int i=1;i<=len;i++)
{
s[++nlen]='#';//扩充原字符串
s[++nlen]=ss[i];
}
s[++nlen]='#';
int id,mx=0;
for(int i=1;i<=nlen;i++)
{
if(i<=mx) f[i]=min(mx-i,f[id-(i-id)]);//继承前面的状态
else f[i]=1;
while(i+f[i]+1<=nlen && i-f[i]-1>=1 && s[i+f[i]+1]==s[i-f[i]-1]) f[i]++;//暴力扩展回文串
if(i+f[i]>mx) id=i,mx=i+f[i];//更新最远边界
}
int ans=0;//计算实际回文串长度
for(int i=1;i<=nlen;i++)
{
int tmp=f[i]/2*2;
if(s[i]!='#') tmp++;
ans=max(ans,tmp);
}
printf("Case %d: %d\n",++T,ans);
}
return 0;
}