【学习笔记】manacher算法
一.关于manacher
manacher算法用于求解一个字符串的回文子串半径长度。
它可以线性地求解对于字符串中的每一个字符,以它本身为中心的最长回文串的半径。
而且这个回文串的每一个以这个字符为中心的子串都是回文串。
这个算法的时间复杂度为O(n)。
二.回文子串长度的奇偶性带来的问题。
当一个回文串长度是奇数的时候,它必然有一个回文中心,回文中心两侧的部分是相互对称的。
那么 当一个回文串长度是偶数的时候,它没有回文中心……,那么该怎么办呢?
我们考虑人为构造一个回文中心。
构造的方法就是在原字符串的每两个字符中间插入相同的一个原串中必定没有的字符,这样我们就可以发现:
当原串的回文子串长度为奇数,它的回文中心是原串中的字符。
当原串的回文子串长度为偶数,它的回文中心是我们新插入的字符。
好了。完美解决了这个问题。
三.manacher算法流程。
我们再次确认一下,manacher算法用于求解对于字符串中的每一个字符,以它本身为中心的最长回文串的半径。
我们把字符串中的每一个字符,以它本身为中心的最长回文串的半径记入一个数组叫p[]。
时间复杂度O(n)要完成这个事,就必须通过递推的方式求解p数组。
1.回文串的对称性
上文我们提到回文串具有对称性。
我们假设现在有一个字符串:s[11]:abababababa
我们显然可以看出这个字符串自己就是一个回文串,这个回文串的回文中心是s[5](下标从0开始)。
观察s[2]为中心的回文串,是不是和s[8]为中心的相等长度的回文串完全相等?
2.两个重要的参数:MaxR,Mid.
所以我们可以通过p[2]推到p[8],但这些都有个前提条件,即p[5]≥4。
因为前提是 以一个大回文串的回文中心为对称点的两个子回文串中心,它们所能拓展出的最长回文串的半径一定是相同的。
所以我们要维护大回文串的右端点MaxR和中心Mid来确保转移。
但这样的转移不一定使得p[]是最长回文半径,所以我们进行暴力拓展就行了。
整体复杂度O(n)。
三.代码
注意在向外拓展的过程中 字符串的左侧和右侧要赋上不同的符号,否则可能会TLE。
洛谷【模板】manacher算法:
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long n,ans,p[22000002]; 4 char c[11000001],s[22000002]; 5 int main() 6 { 7 scanf("%s",c+1); 8 n=strlen(c+1); 9 for(int i=1;i<=n;i++) 10 { 11 s[i*2]=c[i];s[i*2-1]='!'; 12 } 13 s[0]='+'; 14 s[2*n+1]='!'; 15 s[2*n+2]='-'; 16 long long MaxR=0,Mid=0; 17 for(int i=1;i<=n*2;i++) 18 { 19 if(i<MaxR) p[i]=min(MaxR-i,p[Mid*2-i]); 20 else p[i]=0; 21 while(s[i-p[i]-1]==s[i+p[i]+1]) p[i]++; 22 if(i+p[i]>MaxR) 23 { 24 MaxR=i+p[i]; 25 Mid=i; 26 } 27 } 28 for(int i=1;i<=n*2;i++) 29 { 30 ans=max(ans,p[i]); 31 } 32 printf("%lld",ans); 33 }
四.相关习题
1.洛谷 P1659
本题相对比较简单。因为题中规定了所有的回文串都为奇数,所以都不用插板了。
因此我们可以暴力跑Manacher,然后倒着枚举长度,用快速幂统计答案就好了。
1 #include<bits/stdc++.h> 2 #define mod 19930726 3 using namespace std; 4 long long n,k,cnt[1000001],p[1000001],ans=1; 5 char s[1000001]; 6 long long QUICKPOW(long long a,long long b) 7 { 8 long long res=1; 9 while(b) 10 { 11 if(b&1) 12 { 13 res=res*a%mod; 14 } 15 a=a*a%mod; 16 b>>=1; 17 } 18 return res%mod; 19 } 20 int main() 21 { 22 scanf("%lld%lld",&n,&k); 23 scanf("%s",s+1); 24 s[0]='-'; 25 s[n+1]='+'; 26 long long MaxR=0,Mid=0; 27 for(int i=1;i<=n;i++) 28 { 29 if(i<MaxR) p[i]=min(MaxR-i,p[Mid*2-i]); 30 else p[i]=0; 31 while(s[i-p[i]-1]==s[i+p[i]+1]) 32 { 33 p[i]++; 34 } 35 if(i+p[i]>MaxR) 36 { 37 MaxR=i+p[i]; 38 Mid=i; 39 } 40 } 41 for(int i=1;i<=n;i++) 42 { 43 cnt[p[i]*2+1]++; 44 } 45 int sum=0; 46 for(int i=n;i>=1;i--) 47 { 48 if(i%2==0) continue; 49 sum+=cnt[i]; 50 if(k>=sum) 51 { 52 ans=(ans*QUICKPOW(i,sum))%mod; 53 k-=sum; 54 } 55 else 56 { 57 ans=(ans*QUICKPOW(i,k))%mod; 58 k-=sum; break; 59 } 60 } 61 if(k>0) ans=-1; 62 printf("%lld",(ans+mod)%mod); 63 return 0; 64 }