【题解】【切开字符串】

P8631 [蓝桥杯 2015 国 AC] 切开字符串

Sol

首先问题可以转化为对每个前缀求出本质不同奇回文子串数,和对每个后缀求出本质不同子串数和本质不同奇回文子串数。

本质不同子串数

对每个后缀求出本质不同子串数,考虑后缀数组。对于从位置i开始的后缀,这个后缀的本质不同子串数即为把i,i+1,,n后缀排序后,两两相邻的LCP,于是可以从后往前把每个后缀不断加进去,每加入一个后缀,就把跟他相邻的后缀的贡献更新,因为每次需要查区间min,所以要用st表维护。但是换个枚举顺序,考虑从1开始,不断从集合中删去1,2,3,的后缀,因为两点之间的min的范围不断变大,只需用链表就可以线性维护。

本质不同奇回文子串数

对于本质不同奇回文子串数,首先有个结论:长度为n的字符串最多有n个本质不同的回文子串。

证明:

考虑把每个本质不同的回文子串记录在其第一次出现的位置的右端点上。
假设右端点r上,存在两个长度不同的回文子串,长度分别为p,q,不妨设p<q,那么由于两个串都回文,可以得到长度为p的串在iq+p就出现过,与前提矛盾,因此每个右端点最多记录一个回文子串。

求法:

通过证明可以看出每个位置记录的回文子串一定是最长的那个,先通过manacher对每个回文中心x求出其回文半径px,然后对每个(x,px),令[x,x+px1]对x取min,最后每个位置得到的即为最长的奇回文子串的回文中心,把每个右端点对应的回文子串hash一下,就可以统计每个前缀中本质不同的奇回文子串数。区间取min发现可以拓展到一个前缀取min,因此,也可以做到线性。

总结:

如果求后缀数组使用线性做法的话,整道题的复杂度即为O(n),不过笔者比较菜,这里用的倍增写法。

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;
}
posted @   glq_C  阅读(93)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示