Manacher小记
前言
皆移植于原csdn博客,略有修改
所讲例题 |
---|
洛谷 3805【模板】manacher算法 |
JZOJ 2682 洛谷 4555 最长双回文串 |
JZOJ 1950 洛谷 1659 拉拉队排练 |
洛谷 3805【模板】manacher算法
题目
找到一个字符串中的最长回文子串
分析
首先字符串的长度为\(n\leq 11000000\),显然只能用\(O(n)\)的时间完成这道题目,首先朴素的方法就是\(O(n^2)\),也就是枚举中间点向外扩展,
但是这样的坏处在哪里呢,就是这样扩展很有可能找不到最优解而浪费了时间,那应该怎么做呢?
那就可以引进一种神奇的算法,manacher
那么这是什么神奇的东西呢,首先对于长度奇偶性可以在字符串中间填充分隔符,这样字符串就一定是奇数,为了避免越界,还要在开头加一个分隔符。
那要记录三样东西,首先是\(p[i]\)表示中点为\(i\)时的最长回文串的半径,然后那么显而易见答案就是\(max\{p[i]-1\}\),那问题是答案怎么算,这是一个大问题
再首先,我们要记录两个东西,\(mid\)表示当前找到的最长回文串的中点,\(mx\)表示该回文串的右边界。
首先对于每一个\(i\),若\(i<mx\)说明还是有希望的,否则就只能像纯模拟一样\(p[i]=1\)
当然不管怎么样还是得加上这句话\(while (s[i-p[i]]==s[i+p[i]]) ++p[i];\)
那\(if (mx<i+p[i]) mx=i+p[i],mid=i;\)是为什么,显然可知是更新最长回文子串
但问题是\(manacher\)如何优化呢,那就是\(i<mx\)的情况了
那么首先设\(j=2*i-mid\),也就是对于\(mid\),\(i\)的对称点,可以通过\((j+i)\div 2=mid\)得到。
那么分类讨论,首先先配两张图(蓝色所示为j的答案范围,浅绿所示为i的答案范围,区间为mid的答案范围)
-
\(j\)的答案范围超出\(mid\)的答案范围,那么如果\(i\)像\(j\)那样求答案,就违背了\(mid\)的答案,故假设不成立,所以\(p[i]=mx-i\)
-
\(j\)的答案范围小于\(mid\)的答案范围,那么如果\(j\)像\(i\)那样求答案,就违背了\(j\)的答案,故假设不成立,所以\(p[i]=p[j]\)
-
\(j\)的答案范围等于\(mid\)的答案范围,则两者均可
综上所述,就可以得到上面的代码
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
char s[22000015]; int n,ans,p[22000015];
inline signed max(int a,int b){return (a>b)?a:b;}
inline signed min(int a,int b){return (a<b)?a:b;}
inline signed manacher(char *s,int len){
rr int maxlen=1,mx=0,mid=0;
for (rr int i=1;i<len;++i){
p[i]=mx>i?min(p[(mid<<1)-i],mx-i):1;
while (s[i-p[i]]==s[i+p[i]]) ++p[i];
if (mx<i+p[i]) mx=i+p[i],mid=i;
maxlen=max(maxlen,p[i]-1);
}
return maxlen;
}
signed main(){
rr char c=getchar(); s[0]='!';
while (!isalpha(c)) c=getchar();
while (isalpha(c)) s[++n]='|',s[++n]=c,c=getchar();
s[++n]='|'; ans=manacher(s,n);
return !printf("%d",ans);
}
洛谷 4555 最长双回文串
题目
输入长度为\(n\)的串\(S\),求\(S\)的最长双回文子串\(T\),即可将\(T\)分为两部分\(X\),\(Y\),\((|X|,|Y|≥1∣X∣,∣Y∣≥1)\)且\(X\)和\(Y\)都是回文串。
分析
那么在manacher的同时,可以记录最长回文串的最右端的长度\(l[i+p[i]-1]=p[i]-1\)和最左端的长度\(r[i-p[i]+1]=p[i]-1\),
然后用O(n)的时间更新,也就是\(l[i]=max\{l[i+2]-2,l[i]\},r[i]=max\{r[i-2]-2,r[i]\}\)
为什么呢,因为首先最长回文串内的回文子串答案是没有更新的,那为什么要更新呢,
因为最长双回文串的两个回文串不一定是最长的,那么统计\(max\{l[i]+r[i]\}\)即可
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
char s[200015]; int n,ans,p[200015],l[200015],r[200015];
inline signed max(int a,int b){return (a>b)?a:b;}
inline signed min(int a,int b){return (a<b)?a:b;}
inline void manacher(char *s,int len){
rr int mx=0,mid=0;
for (rr int i=1;i<len;++i){
p[i]=mx>i?min(p[(mid<<1)-i],mx-i):1;
while (s[i-p[i]]==s[i+p[i]]) ++p[i];
if (mx<i+p[i]) mx=i+p[i],mid=i;
l[i+p[i]-1]=max(l[i+p[i]-1],p[i]-1);
r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1);
}
for (rr int i=1;i<=len;i+=2) r[i]=max(r[i],r[i-2]-2);
for (rr int i=len;i>0;i-=2) l[i]=max(l[i],l[i+2]-2);
for (rr int i=1;i<=len;i+=2) if (l[i]&&r[i]) ans=max(ans,l[i]+r[i]);
}
signed main(){
rr char c=getchar(); s[0]='!';
while (!isalpha(c)) c=getchar();
while (isalpha(c)) s[++n]='|',s[++n]=c,c=getchar();
s[++n]='|'; manacher(s,n);
return !printf("%d",ans);
}
洛谷 1659 拉拉队排练
题目
问前\(k\)个最长奇回文串长度的乘积
分析
在manacher的基础上开个桶统计长度个数,当然是一个前缀和,因为每个最长回文串其实包含着一些回文子串,然后一定要记得快速幂
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int mod=19930726; typedef long long ll;
char s[2000015]; int n,p[2000015],b[2000015];
inline signed max(int a,int b){return (a>b)?a:b;}
inline signed min(int a,int b){return (a<b)?a:b;}
inline void manacher(char *s,int len){
rr int mx=0,mid=0;
for (rr int i=1;i<len;++i){
p[i]=mx>i?min(p[(mid<<1)-i],mx-i):1;
while (s[i-p[i]]==s[i+p[i]]) ++p[i];
if (mx<i+p[i]) mx=i+p[i],mid=i;
if (!(p[i]&1)) ++b[p[i]-1];
}
}
inline ll ksm(ll x,ll y){
rr ll ans=1;
for (;y;y>>=1,x=(x*x)%mod)
if (y&1) ans=(ans*x)%mod;
return ans;
}
signed main(){
rr ll res=1,k;
scanf("%*d%lld",&k);
rr char c=getchar(); s[0]='!';
while (!isalpha(c)) c=getchar();
while (isalpha(c)) s[++n]='|',s[++n]=c,c=getchar();
s[++n]='|'; manacher(s,n);
for (rr int i=n,sum=0;i>0;i-=2){
sum+=b[i];
if (k>=sum){
res=(res*(ksm(i,sum)))%mod;
k-=sum;
}else{
res=(res*(ksm(i,k)))%mod;
k=0; break;
}
}
if (k>0) res=-1;
return !printf("%lld",res);
}