Manacher模版

六眼飞鱼酱① 提交于 2019-11-27 12:04:06

现在讲的也是一种处理字符串的方法,叫做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 非注释版

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!