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