Manacher算法
作用
Manacher算法(又名“马拉车”算法)主要用于解决一类常用问题——最长回文子串(很多公司笔试都有这类题)。效率为O(n),线性的效率可以秒杀所有低能算法。
实现
首先关于回文串的长度有可能是奇数也有可能是偶数。通常情况下这个问题需要分类讨论,但是这真的很烦很烦。而Manacher算法对于这个细节问题采用了一种巧妙的方法:在每两个字符,以及字符的开始和结束部分都插入一个分隔字符(一定要在原串中未出现过,常用的比如’#’),使得回文串长度是奇数和偶数的情况统一起来。
接下来就是正式开始计算的步骤了,设p[i]=(以i为中心的最长回文子串长度+1)/2,也就是从i开始到以i为中心的最长回文子串的右边界之间的字符个数,这样定义的好处是:以i为中心的最长回文子串长度就是p[i]-1,那么如果p数组已求出,就可以在最后O(n)扫一趟max(p[i]-1)就是答案。那么现在问题就变为在O(n)的时间内求出p数组。
定义两个变量id,mx,其中mx是所有已求出来的所有最长回文子串的右边界中最远的右边界,而id是mx这个右边界对应的中心编号,即有mx=p[id]+id。那么就会出现一个神奇的结论:
如果mx>i,则p[i]>=min(p[2*id-i],mx-i);
这是啥?
好吧写详细点
j=2*id-i; //也就是j是i关于id对应的那个字符
if (mx - i > P[j])
P[i] = P[j];
else /* P[j] >= mx - i */
P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。
附图一张:
如图,在上面的情况就是mx-i>p[j]的情况,j的最长回文子串包含在id的最长回文子串内,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j]。
而如果p[j]>mx-i的时候,就是下面的情况,此时j的最长回文子串未完全包含在以id为中心的最长回文子串中,那么只有包含的白色部分满足这一性质,不包含的橙色部分因为j的左边橙色和i右边橙色可能不同,所以不能完全照搬j的回文子串,不过包含的白色部分的回文子串还是可以照搬的,然后由于你对i的右边还不了解,所以还是要一个一个的左右扩展(这就会为什么p[i]>=min(p[2*id-i],mx-i)是>=而不是=),不过和低效算法相比,这种算法已经利用已求出的回文子串减少了大量的冗余的计算,算是比较优秀的算法了。每次计算出p[i]后就比较i的回文子串右边界,如果比原有的远,更新mx和id。关于复杂度会比O(n)大一点,不过也很好了。
代码
模板提链接:hihocoder1032 最长回文子串
#include<cstdio>
#include<cstring>
#define maxn 2000005
using namespace std;
char s[maxn],st[maxn];
int p[maxn],tst,n,ans,id,mx;
int _min(int x,int y){return (x<y)?x:y;}
void manacher_get(){
mx=ans=0;
for (int i=1;i<=n;i++){
if (mx>i) p[i]=_min(p[2*id-i],mx-i); else p[i]=1;
while (i-p[i]>=1&&i+p[i]<=n&&s[i-p[i]]==s[i+p[i]]) p[i]++; //防越界
if (p[i]+i>mx) {id=i; mx=p[i]+i;}
}
for (int i=1;i<=n;i++)
if (ans<p[i]-1) ans=p[i]-1;
}
int main()
{
freopen("palid.in","r",stdin);
freopen("palid.out","w",stdout);
scanf("%d",&tst);
while (tst--){
scanf("%s",st+1);
n=strlen(st+1); s[1]='#';
for (int i=1;i<=n;i++){
s[i*2]=st[i];
s[i*2+1]='#';
}
n=2*n+1;
manacher_get();
printf("%d\n",ans);
}
return 0;
}
备注
由于作者水平极其有限,有错望各位神犇指出。