manacher算法解析
manacher是很简单的字符串回文算法,作用是O(n)求出一个字符串的最长回文子串
下面给出这一算法的详解
首先,我们设原字符串是aaabba,很显然,这个字符串最长的回文子串长度为4
那么我们就要思考一种算法来计算出这个长度
于是manacher横空出世
首先,我们知道,一个回文子串一定有一个对称轴(或者你叫对称中心?),所以正常来讲,如果想O(n)求出最长回文子串,我们只需枚举每个对称轴,然后O(1)递推即可
可是现在就产生了一个问题:如果原字符串是上面说的那样,那...对称轴是在ab中间的啊,这怎么找出来啊
所以manacher需要一个操作,即在所有中间的位置插入一个'#'之类的无用字符,这样我们就可以忽略掉对称轴在两个字符中间的情况,只考虑对称轴落在字符上的情况即可
所以我们记p[i]表示以i为回文中心,回文半径为p[i]
然后我们考虑递推
怎么推?
我们记录一个maxp,表示在i之前所有的回文中心中,回文右端点最远在哪,记p0表示这个最远的右端点所对应的回文中心
看蒙了?举个例子:
(图中黑线表示字符串,p0为回文中心,maxp为回文右端点)
接下来,我们考虑如何用这个东西维护p[i]
这是需要分类讨论的,即:
①:
若i<maxp,有:
p[i]=min(p[2*p0-i],p[p0]+p0-i);
这一点是显而易见的:如果i<maxp,那么我们显然可以找出一个j,使j与i关于p0对称,于是j=2*p0-i
而根据对称性,j周围的字符一定和i周围的字符相同,所以p[i]可以由p[j]来更新
如图所示,由于对称性,所以i右侧一部分和j左侧一部分相同,同时i左侧一部分和j右侧一部分相同,同时根据j的对称性,j左右两侧相同,故i左右两侧相同,为回文。
至于另一个也很好理解,就是从i到maxp的一段,假设他整个都能构成回文。这一值和刚才对称求出的一个值取最小值才能保证结果的合理性。
②:i>=maxp
没什么办法,暴力吧..
接下来谈谈暴力:
其实无论是情况①还是情况②都需要暴力,因为我们无法保证①求出的是最大值,我们只能保证①求出的是一个可能的合法答案
所以我们需要暴力从p[i]的已有值开始向下跳,跳到不能跳为止即可
剩下的就是代码了
比较好些
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
char a[51000005];
char s[102000005];
int p[51000005];
int maxp,p0,l;
int ans=1;
void manacher()
{
for(int i=1;i<l;i++)
{
if(i<maxp)
{
int j=(p0<<1)-i;
p[i]=min(p[j],p[p0]+p0-i);
}else
{
p[i]=1;
}
while(s[i-p[i]]==s[i+p[i]])
{
p[i]++;
}
if(i+p[i]>maxp)
{
p0=i;
maxp=i+p[i];
}
}
}
int main()
{
scanf("%s",a+1);
l=strlen(a+1);
p[1]=1;
s[0]=s[1]='#';
for(int i=1;i<=l;i++)
{
s[2*i]=a[i];
s[2*i+1]='#';
}
l=(l<<1)+2;
s[l]='0';
manacher();
for(int i=1;i<=l;i++)
{
ans=max(ans,p[i]-1);
}
printf("%d\n",ans);
return 0;
}