zoukankan      html  css  js  c++  java
  • Manacher模版

    现在讲的也是一种处理字符串的方法,叫做Manacher,有点像“马拉车”

    1179: [视频]【Manacher】最长回文子串

    时间限制: 1 Sec  内存限制: 128 MB
    提交: 209  解决: 120
    [提交] [状态] [讨论版] [命题人:admin]

    题目描述

    【题意】
    给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文子串的长度.
    回文就是正反读都是一样的字符串,如aba, abba等
    【输入格式】
    输入有多组数据,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
    每组数据之间由空行隔开(该空行不用处理)
    字符串长度len<=100000
    【输出格式】
    每一行一个整数x,对应一组数据,表示该组数据的字符串中所包含的最长回文子串长度.
    【样例输入】
    aaaa

    abab
    【样例输出】
    4 3
     

    朴素做法:3重for循环:第一重枚举开头,第二重枚举结尾,第三重用来判断是不是回文串,记录答案更新最大值

    但是我们的数据范围是10万,三重for循环是不可能不炸的

    然后就要用到我们的Manacher,但是这道题也可以用后缀数组来做,不过Manacher更简单方便,下面我们来看一下“马拉车” 

                      a w e r s e s s a

                  # a # w # c # r # s # e # s # s # a #

    我们看到一个串,要判断他的串时,我们就要分他的长度是单数还是复数的情况,为什么呢?因为我们的回文是有两种情况的,单数和偶数的情况是不一样的
    (非常不方便)
    所以我们在头和尾,以及两个字符之间加一个#号,(其他的也可以,只要是字符串当中没有出现过的就可以)
    那么经过这个操作之后,不过原来是偶数还是奇数,加上#号之后都会变成奇数,这样我们处理起来就会方便很多

                # a # w # c # r # s # e # s # s # a #
             p[ ]=  1 2 1 2 1 2 1 2 1 2 1 4 1 2 3 2 1 2 1
    首先#号是自己一个,所以是1
    a是与# a #构成,所以是2,剩下的都是同样的道理
    一直到e的出现,他是 # s # e # s # 所以是4,这样我们就知道了p数组的实现了

    p[i]表示以i为中心构成的最长回文字串的结尾与i的距离(结果要+1,因为算上了i)

    然后我们看回e,是第12个字符,p[12]=4,然后他的这个回文串的长度是7,向右延伸4个,向左延伸4个,重复算了1个,所以就是4*2-1=7(包括#号)
    然后因为答案是不算#号的,所以真正的是 (7-1)/2=3
    因为# s # e # s #头尾都有#号,所以删掉一个之后,就是一个#号与一个数字搭配,这样就可以直接除以2算出字串的真正长度

    所以我们发现最长回文字串的长度就是
    =(p[i]*2-1-1)/2
    =(p[i]*2-2)/2
    =p[i]-1  (一定要记住这个最后的变式,特别重要)

    看了我前面博客的一定都知道,这是我目前在讲字符串专题的时候用到的图是最简单的了

    然后我们看到图
    首先字符串的长度是1~len
    l,r表示之前计算的最长回文字串的开头和结尾,r代表结尾所到达的最大的地方,l代表开头
    pos代表回文串的中心

    分类讨论 (有两种情况)

    1.i<r
    我们可以发现p[i]=p[j]
    因为l~r是一个回文串,那么我们就可以直接得出p[i]=p[j],那么p[i]就可以直接继承了

    2.i>=r
    这个时候就不能直接继承了,因为我们不知道i后面的p数组是怎样的,所以我们只能直接暴力求出p[i]
    然后j这个位置我们可以

    p[i]=min(p[j],r-i)
    因为有时候i所构成的最长回文字串是比r还要长的,那么这个时候我们就只能继承i~r这个长度,不能继承到后面,因为我们根本不知道绿色格子的长度是不是相等的,所以我们不能继承到后面
    有因为i与j对称,所以直接p[j]就可以了

    然后j的话,我们可以用 pos-(i-pos)=pos*2-i
    因为i到pos的距离等于j到pos的距离,因为i是以pos为中心和j对称,也就是j是i的对称点

    所以我们可以直接用pos*2-i=j来确定j的位置 

    然后Manacher讲到这里的时候,我们的这道题就已经解决的差不多了,剩下的就是看代码的实现

    (注释版 因为这个相对于EXKMP要好理解很多,所以可以先试着自己打一遍,再看注释深刻理解)

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<algorithm>
     5 #include<cmath>
     6 #include<iostream>
     7 using namespace std;
     8 char s[200010],tt[200010];/*tt表示新字符串,s表示原字符串*/
     9 int p[200010],ans;
    10 void Manacher()
    11 {
    12     int len=strlen(s+1);
    13     for(int i=1;i<=len;i++) tt[2*i-1]='#',tt[2*i]=s[i];/*构造带#新字符串*/
    14     len=len*2+1;/*新字符串的长度*/
    15     tt[len]='#';/*最后面也是#号*/
    16     int pos=0,r=0;
    17     /*r为之前计算的最长回文字串的结尾所到达的最大地方(右端点的最大值)
    18      pos表示r所在最长回文字串的中心*/
    19     for(int i=1;i<=len;i++)
    20     {
    21         int j=2*pos-i;/*j为i的对称点*/
    22         if(i<=r) p[i]=min(p[j],r-i);
    23         /*
    24         如果i<=R的话,分两种情况
    25         第一种情况p[j]>R-i时,表示j对称点的最长回文子串已经越出R的界限了
    26             这时,因为我们不确定大于R时的情况,所以p[i]暂时等于R-i
    27         第二种情况p[j]<=R-i时,那就可以直接继承p[j]得p[i]=p[j]
    28         综合以上两种情况,我们可以用p[i]=min(p[j],R-i)来归纳
    29         */
    30         else p[i]=1;/*不然就为1,就是自己成为一个回文串*/
    31         while(1<=i-p[i] && i+p[i]<=len && tt[i-p[i]]==tt[i+p[i]]) p[i]++;
    32         /*i-p[i]就是i的前面几个位置>=1,而且i的右边几个位置<=len,也就是说我构成的p[i]长度的是不会不合法的,
    33         如果i-p[i]的字符等于i+p[i]的话,也就是说我的回文是成立的,变长了,那么长度增加*/
    34         if(i+p[i]>r)/*右端点比r大*/
    35         {
    36             pos=i;/*把pos定为i*/
    37             r=i+p[i];/*更新r*/
    38         }
    39     }
    40     ans=0;
    41     for(int i=1;i<=len;i++) ans=max(ans,p[i]-1);/*p[i]-1就是真正回文串的长度
    42     那么有#号怎么办,有#号也没有关系,因为p[i]-1终究是没有#号的,其实这个我们直接推出来的过程,而且记录的是最大值,
    43     所以的话,根本不会出现这种情况*/
    44     /*
    45     首先当前的最长回文子串长度为2*p[i]-1
    46     因为我们得到的p数组是在加了#号后的字符串上操作的,所以我们要对答案进行处理
    47     因为#号处于首尾和每个字符之间,所以我们就可以保证所得出的最长回文子串的首尾都为#
    48     这时我们可以得出不带#号的回文串的长度为(2*p[i]-1-1)/2=p[i]-1
    49     所以真正的最长回文子串就是p[i]-1
    50     ans记录最长的回文子串长度
    51     */
    52 }
    53 int main()
    54 {
    55     while(scanf("%s",s+1)!=EOF)
    56     {
    57         Manacher();
    58         printf("%d\n",ans);
    59     }
    60     return 0;
    61 }
    Tristan Code 注释版
     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<algorithm>
     5 #include<cmath>
     6 #include<iostream>
     7 using namespace std;
     8 char s[200010],tt[200010];
     9 int p[200010],ans;
    10 void Manacher()
    11 {
    12     int len=strlen(s+1);
    13     for(int i=1;i<=len;i++) tt[2*i-1]='#',tt[2*i]=s[i];
    14     len=len*2+1;
    15     tt[len]='#';
    16     int pos=0,r=0;
    17     for(int i=1;i<=len;i++)
    18     {
    19         int j=2*pos-i;
    20         if(i<=r) p[i]=min(p[j],r-i);
    21         else p[i]=1;
    22         while(1<=i-p[i] && i+p[i]<=len && tt[i-p[i]]==tt[i+p[i]]) p[i]++;
    23         if(i+p[i]>r)
    24         {
    25             pos=i;
    26             r=i+p[i];
    27         }
    28     }
    29     ans=0;
    30     for(int i=1;i<=len;i++) ans=max(ans,p[i]-1);
    31 }
    32 int main()
    33 {
    34     while(scanf("%s",s+1)!=EOF)
    35     {
    36         Manacher();
    37         printf("%d\n",ans);
    38     }
    39     return 0;
    40 }
    Tristan Code 非注释版
    我们最终都要成长,最终都要和稚嫩的自己告别.
  • 相关阅读:
    如何用代码设置图层在特定比例尺下显示
    typeof
    gauss
    is
    SelectByShape
    layer,featureclass,etc(OGR,原来是讲OGR的,不是ESRI的产品的)
    featurecursor
    rubberband
    渐变弹出层
    [置顶] 任务二 基本的数据库操作
  • 原文地址:https://www.cnblogs.com/Tristanjiang/p/11361770.html
Copyright © 2011-2022 走看看