最长回文字符串_Manacher算法_(O(n))

     朴素算法求最长回文字符串包括奇数长的和偶数长的,求的时候都要分情况讨论,Manacher算法做了一个简单的处理,很巧妙地把奇数长度回文串与偶数长度回文串统一考虑,也就是在每个相邻的字符之间插入一个分隔符,串的首尾也要加,当然这个分隔符不能再原串中出现,一般可以用‘#’或者‘$’等字符。例如:
原串:abaabb
新串:#a#b#a#a#b#b#
这样一来,原来的奇数长度回文串还是奇数长度,偶数长度的也变成以‘#’为中心奇数回文串了。(读者可以自己写着试试,自己检验)

下面我们来讨论算法。

算法思路:把原串每个字符中间用一个串中没出现过的字符分隔开来(统一奇偶),用一个数组p[ i ]记录以 s[i]为中间字符的回文串向右能匹配的长度。先看个例子:(为了防止数组越界我在开头加了一个$,实际上新串就从下标1开始)(参考博客:http://blog.csdn.net/ywhorizen/article/details/6629268)

原串:       a  b  a  a  b  b

新串: $    #   a   #   b   #    a   #   a   #   b  #    b   #

            0   1   2   3   4    5   6   7   8   9  10  11 12  13

p数组:     1   2   1   4   1    2   5   2   1   2   3    2   1

可以看出p[i]-1就是回文串的长度,下面予以证明:

假设以i为中心的回文串长度为S,因为p[ i ]记录以 s[i]为中间字符的回文串向右能匹配的长度,所以有

S=2*p[ i ]-1;

     又因为此时串中加了其他字符#,以s[i]为中心的回文串一定是以#开头和结尾的,以#为中间字符的就是长度为偶数的,以非#号为中间字符的就是长度为奇数的,例如“#b#b#”或“#b#a#b#”所以L 减去最前或者最后的‘#’字符就是原串中长度的2倍,即原串长度为(S-1)/2,化简的P[i]-1。

      接下来就是求p数组了:

        为了防止求P[i]向两边扩展时可能数组越界,我们需要在数组最前面和最后面加一个特殊字符,令P[0]=‘$’最后位置默认为‘’不需要特殊处理。此外,我们用mx 变量记录在求i 之前的回文串中,延伸至最右端的位置,同时用id 记录取这个mx 的id 值。通过下面这句话,算法避免了很多没必要的重复匹配。

if (mx > i){
             p[i] = min(p[2*id - i], maxid - i);
         }

       从左到右计算p[i]时 p[0.....i-1] 都以计算出,并且用一个变量mx记录 max{ k+p[ k ] } (k=0.....i-1),用id记录取最大值时的k, 则 p[ i ]= min( p[2*id - i ], mx - i )(这里需要好好思考,最好自己拿笔画画)

aa

      对于第一幅图以i为中间字符的回文串被以id为中间字符的回文串所覆盖,由对称性,p[ i ] = p[ 2*id - i ] 。对于第二幅图没有完全被覆盖,所以对于k>mx的字符,要一个一个匹配,才能确定p [ i ]。

    下面给出代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2*1000005;
char a[maxn],b[maxn];
int p[maxn];
int main()
{
    int t,m;
    scanf("%d",&t);
    while(t--)
    {
        m=0;
        int ans=0;
        int mx=0,id=0;
        scanf("%s",a);
        int n=strlen(a);
        b[0]='$';
        b[1]='#';
        for(int i=0;i<n;i++)
        {
            b[2*i+2]=a[i];
            b[2*i+3]='#';
        }
        b[2*n+2]='';
        int m=strlen(b);
        for(int i=1;i<m;i++)
        {
            if(mx>i)
                p[i]=min(p[2*id-i],mx-i);
            else
                p[i]=1;
            while(b[i+p[i]]==b[i-p[i]])
            {
                p[i]++;
            }
            if(p[i]+i>mx)
            {
                mx=p[i]+i;
                id=i;
            }
            if(ans<p[i])
                ans=p[i];
        }
        printf("%d
",ans-1);
    }
}





原文地址:https://www.cnblogs.com/Zeroinger/p/5493908.html