首页 > 代码库 > Manacher算法详解

Manacher算法详解

/*首先呢,手动@一下逸安dalao。前几天一是考试没时间,再就是这个算法我只是懂,没写过题所以不敢写一个专题博客。

终于抽出时间写一下这个noip基本不会涉及的算法啦*/

首先我们知道回文的定义:正反读都是一样的字符串叫做回文串。如:madam,lol,oppo,zz,甚至连单字符都可以被称为回文(串)。上面的例子可以看出,回文串可以分为两种:奇数回文和偶数回文。

如果我们想知道以一个字符串的所有子串中,最长的回文串是多长,怎么办呢?

首先想到用裸的枚举中心,然后扩展边界方法来求回文(比如对于hellomadams以d为中心,首先a=a,然后m=m,再然后o!=s找到以d为中心最长回文),我们就会发现第一个难题:偶数回文串并没有以字符为中心。那怎么扩展边界呢?

有一种特别巧妙的方法解决了这个问题。如下:

比如oppovivo这个串,我们在每两个字符之间加上一个特殊符号,通常用#来充当,这样变为了o#p#p#o#v#i#v#o。

那么oppo成为了以第二个‘#’为中心的回文,viv仍是以i为中心的回文。同时我们在首尾加上两个不同的特殊字符,通常就用‘@’,‘$‘。这样连边界的判断都省了不是~到了首或尾,一定会因为字符不同结束扩展。

这部分的代码给一下。

 

void init(int n){
    s[0]=@;
    for(int i=1;i<=n;i++){
        s[i*2-1]=str[i-1];
        s[i*2]=#;
    }
    s[n*2]=$;
}

 

 

 

str是读进来的串,s是修改后的。

 


 

以上部分仅是铺垫manacher的精髓在于:充分利用已有条件,使复杂度降为O(n)。

以下图片均来自于博客http://blog.csdn.net/dyx404514/article/details/42061017

首先,我们要求什么:len数组,len[i]表示以s[i]为中心的最长回文串半径是多少。str转换为s后,所有回文长度一定是奇数。

我们还需要两个变量Po,mx。由于是递推求解,mx表示之前计算中最长回文子串的右端点的最大值,Po表示取得这个最大值的中点。在图中P就是上述mx。

情况一:

i是接下来要求len值的点。设i关于Po的对称点是j,即j=2Po-i。

(1)  i+len[j] < mx.

如图。

技术分享

 

因为i是在当前端点最靠右的回文串之内,所以他一定有一个关于Po的对称点j。i、j位置上的字符一定是一样的。

不仅如此,以j为中心的最大回文串与以i为中心的最大回文串也一定是一样的。len[j]已知,如果i的半径比len[j]大,哪怕是一个字符,根据他们关于Po对称且不在[2Po-mx,mx]范围外,len[j]就还能扩展,len[j]的求解就是矛盾的。

所以此时len[i]=len[j]即可。

(2)  i+len[j] >= mx

如图。

技术分享

与上一种情况同理。在[2Po-mx,mx]范围内的,以i,j为中心的部分是相等的。而边界之外则是一定不同的,否则mx还能更大。

这时候边界以外就好好在扩张一遍,mx外面可能还有i的回文串,别忘了更新Po,mx。

情况二:

i > mx

技术分享

 

啥也不用说了,已经得到的信息卵用没有,老实儿的重新枚举扩展。

这部分的代码:

 

void manacher(int n){
    int mx=0,po=0;
    for(int i=1;i<n*2;i++){//首尾的字符不用管,也不能管。要不该越界了。
        if(mx>i)
        f[i]=min(f[po*2-i],mx-i);
        else f[i]=1;
        while(s[i-f[i]]==s[i+f[i]])
        f[i]++;
        if(f[i]+i>mx)
        mx=f[i]+i,po=i;
    }
}

 

 

 

 

总结

这方面的题还是比较好做,不用涉及太多其他的算法。

最多是在字符串上跑动归,不过出题人想难为我们蒟蒻我们也做不上哈

2017-07-02

 

Manacher算法详解