【模板】manachar算法

推荐\参考题解博客:

https://zhuanlan.zhihu.com/p/70532099 本博客里面插入的图片是来自这篇回答,如有冒犯会立即删除

https://www.cxyxiaowu.com/2665.html

本题解主要为了加深自己体会,可能存在逻辑不通的地方,请读者加以指正:

 

预处理:

在对回文串进行判断的时候,有两种情况:

1是以单个字符为回文中心;

2是以两个字符中间间隙为回文中心;

如果在求回文子串时,每次对这两种情况进行判断,则会导致算法考虑情况过多以及复杂度比较大

一个很好的解决奇数和偶数问题的方法就是,在每个字符之间插入‘#’,并且为了使扩展过程中边界的处理判断,在两端分别插入'^'和‘$'(两个不在字符串中出现的字符),这样在判断两端字符是否相等时,到达边界就必定不会相等,从而跳出循环。进经过处理后的字符串长度永远为奇数。

 

计算辅助数组p

手动计算的方法是“中心扩散法”,此时记录以当前字符为中心,向两边同时扩展所能进行的最大步数。

 

 关于辅助数组的结论:

辅助数组p的最大值就是 最长回文子串的长度

why?

简单理解:

1.新的回文中心是一个字符,那么在原回文子串的中心也是一个字符。在新的回文子串中,向两边扩散是:先分隔符,再字符。扩散的步数由于受到分隔符#的影响,扩散两步实际上只扫到一个有效字符,但是由于对称性,相当于在原始字符串中计算了两个字符。

 

 2.如果新的回文中心为#,那么原始回文中心就是两个字符的间隙。在新的回文子串中,向两边扩散的特点是:先扫描到字符,然后是分隔符“,扩散的步数由于有分隔符#的作用,在新字符串中扩散两步,相当于在原始字符串中扫描到一个字符,但是相当于在原始字符串中计算了左右两个字符。

利用上面的处理,加上中心扩展的方法,时间复杂度是O(n^2),但是这是完全不够的。

Manachar算法,把原本O(n^2)的复杂度优化到线性O(n),极大提升了性能。

如何优化?

在上面的中心扩展中,一个不太智能的地方是:对于新字符串中每一个位置进行扩散,会导致原始字符串每一个字符都被访问多次,类似于KMP优化了前面不必要进行的部分,Manachar算法也是通过原先得到的辅助数组p以及回文串的对称性,将时间复杂度压缩到线性。

具体做法:

1.原字符串的下标:

用p的下标i减去p[i],再除以2,得到原字符串的开头下标

2.求每个p[i](重点):

用center表示回文串的中心,maxRight表示回文串的右边半径,所以maxRight = center + p[i]

注意:

一个center值对应唯一一个maxRight,因此,center和maxRight需要同时更新

 

当我们要求p[i]时,如下图:

i_mirror表示当前需要求的第i个字符关于center对应的下标

现在要求p[ii],如果是中心扩展法,就直接向两边扩展对比就行,但是也可以利用回文串的对称性:

i关于c的对称点是i_mirror,p[i_mirror]=3,所以p[i]=3;

 

但是有三种情况会造成直接复制P[i_mirror]是不正确的:

1.超出了maxRight

 

 如上图情况,在我们求p[i]的时候,p[i_mirror]=7,但是此时p[i]并不等于7.

当我们从i开始往后数7个,等于22,已经超过了maxRight的范围,此时就不能利用对称性了。

但是此时我们一定能扩展到maxRight的位置,所以p[i]至少等于maxRight-i

之后我们任然需要比较T[maxRight+1]和它关于i的对称点,然后像中心扩展法一样一个个去扩展

 

2.p[i_mirror]遇到了原字符串的左边界

 

 此时p[i_mirror]=1, 但是p[i]赋值称1是不正确的

这种情况是因为p[i_mirror]在扩展的时候首先是"#" == "#",之后遇到了"^"和另一个字符比较,遇到了边界,然后终止了循环

但是p[i]并没有遇到边界,所以我们可以继续通过中心扩展法去向两边进行扩展

 

3.i等于了maxRight

此时我们先把p[i]赋值为0,然后通过中心扩展法一步步扩展就好

 

4,考虑center和maxRight 的更新

一步步求初每个p[i], 当求初p[i]的右边界大于当前maxRight时,就需要更新center和maxRight为当前的回文串了

我们必须保证 i 在 maxRight 里面,所以右更右边的maxRight就要更新maxRight

 


 

 

整篇思路理清下来,我觉得优化思想和KMP相类似, 都是直接去除重复的判断步骤来进行优化

具体代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=32000005;
char s[maxn],st[maxn];
int p[maxn];
int change()
{
    int len=strlen(st);
    int j=2;
    s[0]='^';
    s[1]='$';
    for (int i=0;i<len;i++)
    {
        s[j++]=st[i];
        s[j++]='$';
    }
    s[j]='&';
    return j;
}
int Manacher()
{
    int len=change(),mid=1,mx=1,ans=-1;
    for (int i=1;i<len;i++)
    {
        if (i<mx)
            p[i]=min(mx-i,p[mid*2-i]);
        else
            p[i]=1;
        while (s[i-p[i]]==s[i+p[i]])
            p[i]++;
        if (mx<i+p[i])
        {
            mid=i;
            mx=i+p[i];
        }
        ans=max(ans,p[i]-1);
    }
    return ans;
}
int main()
{
    scanf("%s",st);
    printf("%d",Manacher());
}

 

posted @ 2020-11-07 17:15  我不秃  阅读(172)  评论(0编辑  收藏  举报