https://winniechen.cn/?p=208 可能这里的更好看一点?
manacher算法
理论上,我觉得这个算法应该是算作DP的一种,这是我的写法,还请不要嘲笑,毕竟这玩意是我自己YY出来的...
manacher算法,用来求最大每个回文中心的最大回文半径...这个东西,我们发现可以通过DP来实现...
我们考虑,维护一个now满足在现在[1,i-1]中now+f[now] ge f[1...i-1]+1...i-1,现在我们就可以得到一个目前可以的最靠后的回文串...之后针对i 满足f[i]=min(f[(now<<1)-i],f[now]+now-i)也就是求一个已知串中最长的一个回文串,之后在向两侧拓展即可...
时间复杂度的证明:因为最多会拓展n次,所以时间复杂度为O(n)的...
另外,值得注意的是,如果求的回文串长度可以为偶数,就需要在原串的空白位置加上一个特殊字符...比如说#什么的...
例题时间
BZOJ 2565: 最长双回文串
题目很显然,是找到一个#的左侧回文串长度+右侧回文串长度最大...
那么我们可以发现,在维护f[i]数组的同时,维护一个l[i],r[i]的差分表示以i的左右边界的最长回文串...
最后差分一下,l[i]=max(l[i+1]-1,l[i]);之后统计答案...
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> #include <iostream> #include <cstdlib> using namespace std; #define N 200005 char s[N],str[N]; int n,m,g[N],h[N],f[N],ans=2; int main() { scanf("%s",s+1);n=strlen(s+1);memset(g,0x3f,sizeof(g)); for(int i=1;i<=n;i++)str[++m]='#',str[++m]=s[i];str[++m]='#';f[1]=0; for(int i=1,now=1;i<=m;i++) { f[i]=min(f[(now<<1)-i],f[now]+now-i); for(;i-f[i]-1>0&&i+f[i]+1<=m;) { if(str[i-f[i]-1]!=str[i+f[i]+1])break; f[i]++; }//printf("%d ",f[i]); if(f[i]+i>f[now]+now)now=i; g[i+f[i]]=min(g[i+f[i]],i-f[i]); h[i-f[i]]=max(h[i-f[i]],i+f[i]); } for(int i=1;i<=m;i++)h[i]=max(h[i-1]-1,h[i]); for(int i=m;i;i--)g[i]=min(g[i],g[i+1]+1); for(int i=1;i<=m;i++)if(str[i]=='#')ans=max(ans,(h[i]-g[i]+1)>>1); printf("%d ",ans);return 0; }
BZOJ 3790: 神奇项链
题目很有问题,但是我最后理解了...求用最少的回文串拼接成原串...
我们把每个$i$为回文中心的回文串拿出来,之后贪心的求一下最小区间覆盖即可。
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> #include <iostream> #include <cstdlib> using namespace std; #define N 200005 char s[N],str[N]; int n,m,f[N],ans=2; // void fix(int x,int v){for(;x;x-=x&-x)minn[x]=min(minn[x],v);} // int find(int x){int ret=1<<30;for(;x<N;x+=x&-x)ret=min(minn[x],ret);return ret;} struct node{int l,r;}a[N]; bool cmp(const node &a,const node &b){return a.l==b.l?a.r>b.r:a.l<b.l;} int main() { while(scanf("%s",s+1)!=EOF) { n=strlen(s+1);m=0; for(int i=1;i<=n;i++)str[++m]='#',str[++m]=s[i];str[++m]='#';f[1]=0; for(int i=1,now=1;i<=m;i++) { f[i]=min(f[(now<<1)-i],f[now]+now-i); for(;i-f[i]-1>0&&i+f[i]+1<=m;) { if(str[i-f[i]-1]!=str[i+f[i]+1])break; f[i]++; }//printf("%d ",f[i]); if(f[i]+i>f[now]+now)now=i; a[i].l=(i-f[i]+1)>>1,a[i].r=(i+f[i])>>1; // printf("%d %d %d %d ",a[i].l,a[i].r,i,f[i]); }sort(a+1,a+m+1,cmp);int mx=0,ans=0; for(int i=1;mx<n;ans++) { int tmp=0; while(i<=m&&a[i].l<=mx+1)tmp=max(tmp,a[i++].r);mx=tmp; }printf("%d ",ans-1); }return 0; }
BZOJ 2160: 拉拉队排练
题目大意:求出所有的奇数长度的回文串,之后取前K个即可...
我们发现,我们只需要把每个点作为回文中心的最长回文半径求出来,之后差分一下,求一个前缀和...最后一个快速幂即可...
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> #include <iostream> #include <cstdlib> using namespace std; #define N 1000005 #define ll long long #define mod 19930726 char buf[1000005],*p1,*p2; #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000005,stdin),p1==p2)?EOF:*p1++) char s[N];ll K; int n,m,f[N],ans=2,c[N],d[N]; __attribute__((optimize("-O3")))inline void gc() { register int x=0;register char c;while(c<'a'||c>'z')c=nc(); while(c<='z'&&c>='a')s[++x]=c,c=nc(); } __attribute__((optimize("-O3")))ll q_pow(ll x,int n){ll ret=1;for(;n;n>>=1,x=x*x%mod)if(n&1)ret=ret*x%mod;return ret;} __attribute__((optimize("-O3")))int main() { scanf("%d%lld",&n,&K);gc(); f[1]=0; for(int i=1,now=1;i<=n;i++) { f[i]=min(f[(now<<1)-i],f[now]+now-i); for(;;) { if(s[i-f[i]-1]!=s[i+f[i]+1])break; f[i]++; }//printf("%d ",f[i]); if(f[i]+i>f[now]+now)now=i; } for(int i=1;i<=n;i++)c[f[i]+1]--,c[0]++;ll sum=c[0]; for(int i=1;i<=n;i++)c[i]+=c[i-1],sum+=c[i];if(sum<K){puts("-1");return 0;}ll ans=1; for(int i=n;~i;i--) { if(c[i]>=K) { printf("%lld ",ans*q_pow((i<<1)+1,K)%mod); return 0; }K-=c[i];ans=ans*q_pow((i<<1)+1,c[i])%mod; // printf("%d ",c[i]); } }