前言ios
Manacher(也叫马拉车)是一种用于在线性时间内找出字符串中最长回文子串的算法算法
算法spa
通常的查找回文串的算法是枚举中心,而后往两侧拓展,看最多拓展出多远。最坏状况下$O(n^2)$code
然而Manacher可以充分利用回文的性质blog
首先,回文分为奇回文(好比$aba$)和偶回文(好比$abba$),若是分开来讨论会很麻烦。字符串
因而咱们在原串的首尾以及每两个字符之间各插入一个原串中没有出现过的字符。好比$abbbac$,变成$\%a\%b\%b\%b\%a\%c\%$get
那么这样的话,上面的$aba$变成$\%a\%b\%a\%$,$abba$变成$\%a\%b\%b\%a\%$,都变成了奇回文string
咱们定义$p[i]$为以$i$为中心的回文串的最长半径,好比串$\%a\%b\%a\%$,其中$b$为第四个字符,则$p[4]=4$(由于以他为中心的最长回文串是$\%a\%b\%a\%$,而这个回文串的半径为4)io
那么咱们原串中以这个位置为中心的最长回文串长度就是$p[i]-1$(一个要去掉$\%$,而后半径的话要防止中心被多算一次)模板
那么如今的问题就是怎么快速求出$p[i]$了
咱们假设$pos$是一个已经记录的值,$R$为以$pos$为中心的回文串的最长右边界,而后如今要求$p[i]$
$j$是$i$关于$pos$的对称点,也就是说$j=2*pos-i$
那么这个时候分为两种状况
1.$j$的最长回文没有跳出$L$
由于$[L...R]$是一个回文串,因此$i$的回文和$j$的回文相同,即$p[i]=p[j]$
2.$j$的最长回文越过了$L$
若是在这种状况下,$j$的最长回文就不必定是$i$的最长回文了。而后黄色那块确定仍是知足条件的
因此综上,$p[i]=min(p[2*pos-1],R-i)$
而后剩下的那部分怎么办?暴力直接算
我没口胡,真的
时间复杂度
时间复杂度是$O(n)$的
为啥嘞?
若是$p[i]$能直接求确定是$O(1)$的
而后若是须要上暴力那么每一次都能让$R$严格右移
由于串长只有$O(n)$,因此暴力次数最多$O(n)$
上板子吧,板子抄的zzk大爷的
1 // luogu-judger-enable-o2 2 //minamoto 3 #include<iostream> 4 #include<cstdio> 5 #include<cstring> 6 using namespace std; 7 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 8 char buf[1<<21],*p1=buf,*p2=buf; 9 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 10 const int N=1.1e7+5; 11 int p[2*N];char s[N],now[N*2]; 12 inline bool is(char c){return c!='\n'&&c!=EOF;} 13 inline void read(char *s){ 14 int len=0;char ch;while(is(ch=getc())) s[++len]=ch;s[++len]=0; 15 } 16 int manacher(char *s){ 17 int len=strlen(s+1); 18 for(int i=1;i<=len;++i) now[2*i-1]='%',now[2*i]=s[i]; 19 now[len=len*2+1]='%'; 20 int pos=0,R=0; 21 for(int i=1;i<=len;++i){ 22 if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1; 23 while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i]; 24 if(i+p[i]>R) pos=i,R=i+p[i]; 25 } 26 int mx=0; 27 for(int i=1;i<=len;++i) cmax(mx,p[i]-1); 28 return mx; 29 } 30 int main(){ 31 // freopen("testdata.in","r",stdin); 32 read(s); 33 printf("%d\n",manacher(s)); 34 return 0; 35 }
不过我有个地方还没弄明白,就是上面求$p[i]$的时候,我写成这样也能A
1 for(int i=1;i<=len;++i){ 2 if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1; 3 while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i]; 4 if(i+p[i]-1>R) pos=i,R=i+p[i]-1; 5 }
话说若是我写成 p[i]=min(p[2*pos-i],R-i+1) 我还能理解为是开区间和闭区间的不一样……话说一个闭区间一个开区间为啥没问题啊……
先坑着,等会了再来填坑
首先双回文串确定是拼接而成的
那么咱们就记录一下每个字符分别做为回文串的最左端(最右端)时,对应的中心最左(最右)在哪里
而后就能够经过拼接获得双回文串了
1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 using namespace std; 6 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 7 const int N=1e5+5; 8 int p[2*N];char s[N],now[N*2];int L[N<<1],R[N<<1],len,ans; 9 void manacher(char *s){ 10 len=strlen(s+1); 11 for(int i=1;i<=len;++i) now[2*i-1]='%',now[2*i]=s[i]; 12 now[len=len*2+1]='%'; 13 int pos=0,R=0; 14 for(int i=1;i<=len;++i){ 15 if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1; 16 while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i]; 17 if(i+p[i]>R) pos=i,R=i+p[i]; 18 } 19 } 20 int main(){ 21 // freopen("testdata.in","r",stdin); 22 scanf("%s",s+1); 23 manacher(s); 24 for(int i=1,pos=1;i<=len;++i) 25 for(;pos<=i+p[i]-1;++pos) L[pos]=i; 26 for(int i=len,pos=len;i;--i) 27 for(;pos>=i-p[i]+1;--pos) R[pos]=i; 28 for(int i=1;i<=len;++i) cmax(ans,R[i]-L[i]); 29 printf("%d\n",ans); 30 return 0; 31 }
由于题目要求的是奇数回文,因此连$\%$都不必加了,直接上manacher就行了
而后由于一个半径为$n$的回文串存在,那么$n-1$,$n-2$的回文串也一定存在
因此要开一个桶存一下前缀和
而后还要用一下快速幂
而后就差很少了
1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #define ll long long 6 using namespace std; 7 const int N=1e6+5,mod=19930726; 8 char s[N];int p[N],c[N],n;ll ans=1,sum=0,k; 9 inline ll qpow(ll a,ll b){ 10 ll res=1; 11 while(b){ 12 if(b&1) (res*=a)%=mod; 13 (a*=a)%=mod,b>>=1; 14 } 15 return res; 16 } 17 int main(){ 18 // freopen("testdata.in","r",stdin); 19 scanf("%d%lld",&n,&k); 20 scanf("%s",s+1); 21 for(int i=1,pos=0,R=0;i<=n;++i){ 22 p[i]=i<R?min(p[2*pos-i],R-i):1; 23 while(i-p[i]>=1&&i+p[i]<=n&&s[i-p[i]]==s[i+p[i]]) ++p[i]; 24 if(i+p[i]-1>R) pos=i,R=i+p[i]-1; 25 ++c[2*p[i]-1]; 26 } 27 (n&1)?0:(--n); 28 for(int i=n;i;i-=2){ 29 sum+=c[i]; 30 if(sum>k){(ans*=qpow(i,k))%=mod;break;} 31 (ans*=qpow(i,sum))%=mod,k-=sum; 32 } 33 printf("%lld\n",sum<k?-1:ans); 34 return 0; 35 }