「manacher」
前言
发现大神指$skyh$都用$manacher$水回文自动机的题,就学了学这个做法。
应用
- 求以某个点为中心的最长回文半径
- $O(n)$
- 由第一点可以拓展求以某个点为终点/起点的最长回文
- 没了(怎么感觉跟回文自动机差不多的说)
代码
#include<bits/stdc++.h> using namespace std; const int N=3e7; int n,r[N]; char str[N],s[N]; int main(){ scanf("%s",str+1);int l=strlen(str+1); s[0]='/'; s[++n]='%'; for(int i=1;i<=l;++i) s[++n]=str[i],s[++n]='%'; int ans=-0x3f3f3f3f; for(int i=1,maxr=0,thatmid=0;i<=n;++i){ int j=2*thatmid-i; if(i<=maxr) r[i]=min(r[j],maxr-i+1); while(s[i+r[i]]==s[i-r[i]]) ++r[i]; if(r[i]+i-1>maxr) maxr=i+r[i]-1,thatmid=i; ans=max(ans,r[i]); } printf("%d\n",ans-1); }
例题
P1659 [国家集训队]拉拉队排练
求前k长回文。
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=2e6+50,mod=19930726; ll n,k,r[N],len[N]; char s[N],str[N]; ll mgml(ll a,ll b,ll ans=1){ for(;b;b>>=1,a=a*a%mod) if(b&1) ans=ans*a%mod; return ans; } int main(){ scanf("%lld%lld%s",&n,&k,str+1); int l=strlen(str+1); s[n=0]='/';s[++n]='%'; for(int i=1;i<=l;++i) s[++n]=str[i],s[++n]='%'; for(ll i=1,maxr=0,thatmid=0;i<=n;++i){ int j=thatmid*2-i; if(i<=maxr) r[i]=min(maxr-i+1,r[j]); while(s[i+r[i]]==s[i-r[i]]) ++r[i]; if(i+r[i]-1>=maxr) maxr=i+r[i]-1,thatmid=i; if(i&1ll^1ll) len[r[i]-1]++; } int st=(n&1ll)?n:n-1; ll ans=1; for(int i=st;i>=1;i-=2){ len[i]+=len[i+2]; ll Lim=min(len[i],k); ans=ans*mgml(i,Lim)%mod; k-=Lim; if(!k) break; } printf("%lld\n",ans); return 0; }
P4555 [国家集训队]最长双回文串
求以某个点为起点/终点的最长回文。
还是上模板,然后因为要保证两个回文拼接起来不能交叉,于是就枚举'%'作为分界点统计答案。
再计算以某个点为起点/终点的最长回文有个方法,在$manacher$中只把饱和回文加进数组,最后再$O(n)$扫数组向一边更新答案,因为要两个回文拼接起来,因此一个回文的不算。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=4e5+50; int n,r[N],L[N],R[N]; char s[N],str[N]; signed main(){ scanf("%s",str+1); int l=strlen(str+1); s[n=0]='/';s[++n]='%'; for(int i=1;i<=l;++i) s[++n]=str[i],s[++n]='%'; for(int i=1,maxr=0,thatmid=0;i<=n;++i){ int j=thatmid*2-i; if(i<=maxr) r[i]=min(maxr-i+1,r[j]); while(s[i+r[i]]==s[i-r[i]]) ++r[i]; if(i+r[i]-1>=maxr) maxr=i+r[i]-1,thatmid=i; L[i-r[i]+1]=max(L[i-r[i]+1],r[i]-1); R[i+r[i]-1]=max(R[i+r[i]-1],r[i]-1); // printf("%d %d %d %d %d\n",i-r[i]+1,L[i-r[i]+1],i+r[i]-1,R[i+r[i]-1],r[i]-1); } for(int i=1;i<=n;i+=2) L[i]=max(L[i],L[i-2]-2); for(int i=n;i>=1;i-=2) R[i]=max(R[i],R[i+2]-2); long long ans=0; for(int i=1;i<=n;i+=2) if(L[i]&&R[i]) ans=max(ans,(long long)L[i]+R[i]); printf("%lld\n",ans); return 0; }
Keep it simple and stupid.