了解Manacher算法

       这个算法看了半天才理解,在这里做一个不太详细的讲解。

       首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号。比如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#。 为了进一步减少编码的复杂度,可以在字符串的开始加入另一个特殊字符,这样就不用特殊处理越界问题,比如$#a#b#a#。

       下面以字符串12212321为例,经过上一步,变成了 S[] = "$#1#2#2#1#2#3#2#1#"。

       然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系(可以看出,P[i]-1正好是原字符串中回文串的总长度)

S # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #
P 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1

       那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,其中 id 为已知的 {右边界最大} 的回文子串的中心,mx则为id+P[id],也就是这个子串的右边界。

       然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx>i,那么P[i]>=MIN(P[2*id-i],mx-i)。

       还是借助图来理解比较容易。

       当 mx-i>P[j]的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于i和j对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有P[i]=P[j],见下图。

       

       当 P[j]>=mx-i的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说P[i]>=mx-i。至于mx之后的部分是否对称,就只能老老实实去匹配了。

       对于mx<=i的情况,无法对P[i]做更多的假设,只能P[i]=1,然后再去匹配了。

       看一道模板题HDU3068

Code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define N 33000005
using namespace std;
int p[N];char s[N];
int main()
{
    while(~scanf("%s",s))
    {
        int id=0,Max=0,ans=0,len=strlen(s);
        for(int i=len;i>=0;i--)
        {
            s[i+i+2]=s[i];
            s[i+i+1]='#';
        }
        s[0]='$';
        for(int i=1;i<len*2+1;i++)
        {
            if(p[id]+id>i)p[i]=min(p[2*id-i],p[id]+id-i);
            else p[i]=1;
            while(s[i+p[i]]==s[i-p[i]])p[i]++;
            if(i+p[i]>Max)
            {
                Max=i+p[i];
                id=i;
            }
            if(ans<p[i])ans=p[i];
        }
        printf("%d\n",ans-1);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_34531807/article/details/79520640
今日推荐