【题解】【切开字符串】
P8631 [蓝桥杯 2015 国 AC] 切开字符串
Sol
首先问题可以转化为对每个前缀求出本质不同奇回文子串数,和对每个后缀求出本质不同子串数和本质不同奇回文子串数。
本质不同子串数
对每个后缀求出本质不同子串数,考虑后缀数组。对于从位置开始的后缀,这个后缀的本质不同子串数即为把后缀排序后,两两相邻的,于是可以从后往前把每个后缀不断加进去,每加入一个后缀,就把跟他相邻的后缀的贡献更新,因为每次需要查区间,所以要用表维护。但是换个枚举顺序,考虑从开始,不断从集合中删去的后缀,因为两点之间的的范围不断变大,只需用链表就可以线性维护。
本质不同奇回文子串数
对于本质不同奇回文子串数,首先有个结论:长度为的字符串最多有个本质不同的回文子串。
证明:
考虑把每个本质不同的回文子串记录在其第一次出现的位置的右端点上。
假设右端点上,存在两个长度不同的回文子串,长度分别为,不妨设,那么由于两个串都回文,可以得到长度为的串在就出现过,与前提矛盾,因此每个右端点最多记录一个回文子串。
求法:
通过证明可以看出每个位置记录的回文子串一定是最长的那个,先通过manacher对每个回文中心求出其回文半径,然后对每个,令对x取min,最后每个位置得到的即为最长的奇回文子串的回文中心,把每个右端点对应的回文子串hash一下,就可以统计每个前缀中本质不同的奇回文子串数。区间取min发现可以拓展到一个前缀取min,因此,也可以做到线性。
总结:
如果求后缀数组使用线性做法的话,整道题的复杂度即为,不过笔者比较菜,这里用的倍增写法。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; //#define int long long #define fi first #define se second #define pb push_back int rd() { int x=0,w=1; char ch=getchar(); while(!isdigit(ch)&&ch!='-') ch=getchar(); if(ch=='-') w=-1,ch=getchar(); while(isdigit(ch)) x=x*10+ch-'0',ch=getchar(); return x*w; } const int N=2e5+100; int n,sa[N],rk[N],h[N],y[N],t[N],pre[N],suf[N],p[N],f[2][N]; char s[N]; long long w[N]; const int P=37; unsigned long long hs[N],pw[N]; int val[N]; unordered_map<unsigned long long ,int>mp[N]; int geths(int l,int r) { return hs[r]-hs[l-1]*pw[r-l+1]; } void manacher() { p[1]=1; int id=1,mx=2; for(int i=2;i<=n;++i) { p[i]=min(mx-i,p[(id<<1)-i]); while(s[i+p[i]]==s[i-p[i]]) p[i]++; if(i+p[i]>mx) id=i,mx=i+p[i]; } } void calc(int op) { for(int i=1;i<=n;++i) hs[i]=hs[i-1]*P+s[i]-'a'+1; manacher(); for(int i=1;i<=n;++i) val[i]=n; for(int i=1;i<=n;++i) val[i+p[i]-1]=min(val[i+p[i]-1],i); for(int i=n-1;i;--i) val[i]=min(val[i],val[i+1]); for(int i=1;i<=n;++i) { f[op][i]=f[op][i-1]+(!mp[i-val[i]][geths(val[i],i)]); mp[i-val[i]][geths(val[i],i)]=1; } for(int i=1;i<=n;++i) mp[i-val[i]].clear(); } int m=256; void getsa() { for(int i=1;i<=n;++i) t[rk[i]=s[i]]++; for(int i=1;i<=m;++i) t[i]+=t[i-1]; for(int i=1;i<=n;++i) sa[t[rk[i]]--]=i; for(int k=1;k<n;k<<=1) { int cnt=0; for(int i=n-k+1;i<=n;++i) y[++cnt]=i; for(int i=1;i<=n;++i) if(sa[i]>k) y[++cnt]=sa[i]-k; for(int i=1;i<=m;++i) t[i]=0; for(int i=1;i<=n;++i) t[rk[i]]++; for(int i=1;i<=m;++i) t[i]+=t[i-1]; for(int i=n;i;--i) sa[t[rk[y[i]]]--]=y[i]; for(int i=1;i<=n;++i) y[i]=rk[i]; m=0; for(int i=1;i<=n;++i) rk[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?m:++m; if(m==n) break; } for(int i=1,j,k=0;i<=n;h[rk[i]]=k,++i) for(k?--k:0,j=sa[rk[i]-1];s[j+k]==s[i+k];++k); } signed main() { n=rd();pw[0]=1; for(int i=1;i<=n;++i) pw[i]=pw[i-1]*P; scanf("%s",s+1); getsa(); w[1]=1ll*n*(n+1)/2; for(int i=1;i<=n;++i) w[1]-=h[i]; for(int i=1;i<=n;++i) pre[i]=i-1,suf[i]=i+1; suf[n]=0; for(int i=1;i<n;++i) { w[i+1]=w[i]-(n-i+1)+h[rk[i]]; if(suf[rk[i]]) { w[i+1]+=h[suf[rk[i]]]; h[suf[rk[i]]]=min(h[suf[rk[i]]],h[rk[i]]); w[i+1]-=h[suf[rk[i]]]; pre[suf[rk[i]]]=pre[rk[i]]; } if(pre[rk[i]]) suf[pre[rk[i]]]=suf[rk[i]]; } calc(0); reverse(s+1,s+n+1); calc(1); reverse(f[1]+1,f[1]+n+1); long long ans=0; for(int i=1;i<n;++i) { ans=max(ans,f[0][i]*(w[i+1]-f[1][i+1])); } cout<<ans; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具