• Manacher算法详解


    首先我们知道回文的定义:正反读都是一样的字符串叫做回文串。如: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为中心的部分是相等的(阴影部分)。这时i的左边界想再往里扩展,右侧阴影左边字符p与左侧阴影右边字符n是一样的,仍关于Po对称,p=n,而边界之外(左阴影左m,右阴影右q)的字符则是一定不同的,否则len[Po]还能更大,m≠q。但m,n是对称的,m=n,所以p≠q。

    所以如果i+len[j]严格大于mx,len[i]=mx-i。一定的。

    但如果恰好边界重合了,j虽然扩不出去了,但i还有可能。因为m,q一定是不一样的,m,n也不一样,n,p一样。

    q≠m≠n=p,p,q可能相等。这时候边界以外就要好好在扩张一遍,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&&i+f[po*2-i]<mx){
                f[i]=f[po*2-i];
                continue;
            }
            else if(mx>i&&i+f[po*2-i]>=mx){
               f[i]=mx-i;
               if(i+f[po*2-i]>mx)continue;
            }
            else f[i]=1;
            while(check(i-f[i],i+f[i]))
            f[i]++;
            if(f[i]+i>mx)
            mx=f[i]+i,po=i;
        }
    }

    按原意写的代码。但是代码长度还可以缩减,因为即使len[i]=len[j]或者len[i]=mx-i没跑了,放在枚举扩展的while循环里也就是判断一下,就跳出来了。都放在一起,取一下最小值,这样mx > i下的三种情况就不用另行区分了。我测验在1e6范围下时间上也就多不到10ms。可以说不值一提了。代码简略了,考场出错概率也就小了。如下:

    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-08-22更

  • 相关阅读:
    如何把python最小化安装在客户机上面
    c++的历史-异常
    插件系统的构建
    ora-01445:无法从不带保留关键字的表的连接视图中选择ROWID或采样
    转--Oracle 审计和测试操作
    foreach属性-动态-mybatis中使用map类型参数,其中key为列名,value为列值
    C语言中 *.c和*.h文件的区别!
    解决方法:loadrunner 场景下执行webservice脚本是---报错10492 Error: Exception was raised when calling per-process-init function in extens
    NoSQL之Redis高级实用命令详解--安全和主从复制
    Expect安装方法
  • 原文地址:https://www.cnblogs.com/orzzz/p/7106118.html
Copyright © 2020-2023  润新知