• manacher算法详解+模板 P3805


    前言:
    记住manacher是一个很简单的算法。
    首先我们来了解一下回文字串的定义:若一个字符串中的某一子串满足回文的性质,则称其是回文子串。(注意子串必须是连续的,而子序列是可以不连续的)
    那么若给定一长度为n的字符串,要求出最长回文子串的长度,怎么做呢?
    首先想到的是暴力搜索,我就不赘述思路了。那如果n特别大呢?10的7次方怎么做?
    于是,我们需要了解一个贼有意思的鸡肋算法manacher,俗称“马拉车”,为什么说是贼有意思呢?因为它的思路实在是巧妙,又为什么说是鸡肋呢?因为它貌似只适用于求解最大回文子串的问题。
     
    思路:

      首先我们知道回文子串的判定和长度的奇偶性是有关系的,由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,在字符间插入一个字符(前提这个字符未出现在串里),常用的是"$""#"。举个例子:s="abbahopxpo",转换为newS="$#a#b#b#a#h#o#p#x#p#o#"(这里在串首、尾加的字符"$"和"";只是设置边界,为了防止越界,而且显然不影响回文子串,下面会有说明),如此,s 里起初有一个偶回文abba和一个奇回文opxpo,被转换为"#a#b#b#a#"和"#o#p#x#p#o#",长度都转换成了奇数。

      证明经过上述操作回文串的长度必为奇数:

        若原回文串的长度为奇数n(原串 aaa),首尾间共有偶数n-1个空位被加上"#",加上首前和尾后各1个"#"(新串 #a#a#a#),可见新的长度为2n+1,显然是奇数;

        若原回文串的长度为偶数n(原串 aa),首尾间共有奇数n-1个空位被加上"#",加上首前和尾后各1个"#"(新串 #a#a#),可见新的长度也为2n+1,显然是奇数。得证。

      我们定义一个辅助数组int p[]p[i]表示以news[i]为中心的最长回文的半径,例如

      易得P[i]-1即以i为中心的在原串中的回文子串的长度:例如P[5]=5,则4就是以5这个位置为中心在原串中的最长回文子串的长度(abba),为什么这是对的呢?因为我们知道P[i]*2-1为新串中以i为中心的最长回文子串的长度,设该回文子串原长为n,则由上面的证明可知新串的长度=2n+1=P[i]*2-1,移项化简得n=P[i]-1。

      于是重点来了:我们如何快速的求出P[]数组。这时就要用到DP的思想了,我们一般会想到这样求解p[i],先初始化p[i]=1,再以news[i]为中心判断两边是否相等,相等就p[i]++。这就是普通的思维,但是我们想想,能否避免重复操作让p[i]的初始化不是 1,让它更大点,看下图:

      设置两个变量,mx 和 id 。
      mx 代表以news[id]为中心的最长回文最右边界,也就是mx=id+p[id]。

      假设我们现在求p[i],也就是以news[i]为中心的最长回文半径,如果i<mx,如上图,那么

      if(i<mx) p[i] = min(p[id*2-i] , mx-i);      

      else p[i] = 1;

     怎么理解呢?我们看图,因为mx是以id为中心的最长回文半径,若当前的i比mx要小,说明以i为中心的最长回文子串的一部分已经出现在以id为中心的回文子串中了,注意图中下面标注的两条短黑线,因为我们是线性dp,j点一定被访问过且P[j]被处理过,而j与i关于id对称(i+j=2*id),所以j=id*2-i,由于回文串的对称性,以i为中心的最长回文子串中的半径最小值一定是p[j]和mx-i中的最小值; 而若i>mx,就只能将p[i]赋为1来更新了。(感觉解释了和没解释一样啊,由于博主表述能力较差,我们不如举例)

      就比如一个回文子串:"#a#a#a#a#a#",我们以中间的a位置为id,所以id=6,mx=12。假设目前访问到了i=8的位置,则与其对称的j=2*6-8=4,而p[4]在访问8之前已经处理,p[4]=4,mx-i=4,所以取最小值4,将p[8]初始值赋为4。因为很容易看出在当前已经可以确定的是以8为中心的最长回文串的半径至少为4(#a#a#a#),当然有可能更大,我们之后判断news[i+P[i]]==news[i-P[i]]是否成立,若成立就p[i]++。

      讲的好心累啊,我学manacher时完全自学,也没有什么解释,完全是靠自己看懂的,还是得自己结合图和代码理解啊,先发一波核心代码。

    int manacher()
    {
        int len=init();
        int ans=-N,id,mx=0;
        for(int i=1;i<len;i++)
        {
            if(i<mx)p[i]=min(p[id*2-i],mx-i);
            else p[i]=1;
            while(news[i-p[i]]==news[i+p[i]])p[i]++;
            if(mx<i+p[i])id=i,mx=i+p[i];
            ans=max(ans,p[i]-1);
        }
        return ans;
    }

      再发两种情况的图片自行理解一番:

      这是初值p[i]=p[id*2-i]的图:这里写图片描述

       这是初值p[i]=mx-i的图,注意虚线部分:这里写图片描述
     
     
    巨说极其重要的性质

    这里写图片描述
    再来看这张图,我们发现,如果mx不更新,就不会出现本质不同的回文子串,因为前面已经出现过了;而每扩展一次mx,最多新出现一个本质不同的回文子串。
    于是得到性质:一个字符串最多只有n个本质不同的回文子串。这个性质很重要,有些题会用到,需要这个性质去分析。

    (虽然我也不知道有啥用……)

     

    算法复杂度分析:知乎

    我自己简略地讲一下,因为i与mx只有两种情况,而每次检索已经保证了单调递增,可以知道每个点最多被访问两次,while()循环本身的时间复杂度在没有前提条件的情况下确实是O(n)但是这里的r(也就是上面答案中的maxlen),是不断往后走而不可能往前退的,它自身的值的变化是递增的。那么你可以明白,要进入while循环,i的值必然是比r大的,也就是说整个程序结束为止,while循环执行的操作数为n次(线性次),而字符串中的每个字符,最多能被访问到2次。时间复杂度必然为O(n)

     

    终于讲完了,上模板题洛谷P3805

    题目描述

    给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.

    字符串长度为n

    输入输出格式

    输入格式:

    一行小写英文字符a,b,c...y,z组成的字符串S

    输出格式:

    一个整数表示答案

    输入输出样例

    输入样例#1: 
    aaa
    输出样例#1: 
    3

    说明

    字符串长度len <= 11000000

    代码:

    #include<bits/stdc++.h>
    #define il inline
    #define ll long long
    #define debug printf("%d %s
    ",__LINE__,__FUNCTION__)
    using namespace std;
    const int N=23000005;
    char s[N],news[N];
    int p[N];
    il int init()
    {
        int len=strlen(s);
        news[0]='$',news[1]='#';
        int j=2;
        for(int i=0;i<len;i++)news[j++]=s[i],news[j++]='#';
        news[j]='';
        return j;
    }
    il int manacher()
    {
        int len=init();
        int ans=-N,id,mx=0;
        for(int i=1;i<len;i++)
        {
            if(i<mx)p[i]=min(p[id*2-i],mx-i);
            else p[i]=1;
            while(news[i-p[i]]==news[i+p[i]])p[i]++;
            if(mx<i+p[i])id=i,mx=i+p[i];
            ans=max(ans,p[i]-1);
        }
        return ans;
    }
    int main()
    {
        scanf("%s",s);
        printf("%d",manacher());
        return 0;
    }
  • 相关阅读:
    只要实现了IListSource, IEnumerable, or IDataSource的对象都可以作为DataSource进行Binding
    我的博客开张了!
    EnableViewState对Table, TableRow, TableCell不起作用?
    Bubble Sort (c#)
    trigger
    查询数据库中触发器的两种方法
    招聘要求
    调用搜索引擎代码
    JavaScript,只有你想不到
    [RIA]HTML5怎么就成了RIA“杀手”?
  • 原文地址:https://www.cnblogs.com/five20/p/8610733.html
Copyright © 2020-2023  润新知