NOI 2016 优秀的拆分
这题分的算法很好想。但难点在于剩下分。
我们将AABB
分成前后两个部分,AA
和BB
。那么我们只需计算,分别表示以结束&开头的形如AA
的子串个数。答案就是。
计算可以枚举+hash,具体不多说了。时间复杂度为。
那么这题就转化成如何快速求得和。
我们可以枚举最终AA
子串的A
的长度,然后在处打上标记。
可以发现一个有趣的性质——长为的AA
子串,一定经过恰好个相邻的打上标记的下标。(抽屉原理)
(至于为什么要往这方面思考,我不知道:(
那么,我们枚举后一个下标,前一个下标就是,我们求它们的longest common prefix和longest common suffix。设其长度为。
如果,那么两个区间不相交,不对任何造成贡献。
否则,我们可以得到一段区间,可以作为AA
的开头;还可以得到长度相同的区间作为结尾,那么我们对这两个区间,这可以使用差分维护。
时间复杂度为或,看实现lcp和lcs时用的是SA还是二分+hash。
下面是二分+hash的的解法:
#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl
using ll=long long;
const int maxn=30005;
int n;
const ll mod1=998244353,mod2=1000000007;
const ll b1=200061018,b2=20061009;
struct stringhash {
ll h1[maxn],h2[maxn];
ll p1[maxn],p2[maxn];
void init(char *S) {
int len=strlen(S+1);
p1[0]=p2[0]=1; h1[0]=h2[0]=0;
for(int i=1;i<=len;i++) p1[i]=p1[i-1]*b1%mod1;
for(int i=1;i<=len;i++) p2[i]=p2[i-1]*b2%mod2;
for(int i=1;i<=len;i++) h1[i]=(h1[i-1]*b1+S[i])%mod1;
for(int i=1;i<=len;i++) h2[i]=(h2[i-1]*b2+S[i])%mod2;
}
std::pair<ll,ll> gethash(int l,int r) {
ll ret1=h1[r]-h1[l-1]*p1[r-l+1]%mod1+mod1;
ll ret2=h2[r]-h2[l-1]*p2[r-l+1]%mod2+mod2;
return std::make_pair(ret1%mod1,ret2%mod2);
}
void clearall() {
memset(h1,0,sizeof h1); memset(h2,0,sizeof h2);
memset(p1,0,sizeof p1); memset(p2,0,sizeof p2);
}
}solver;
int Q(int l,int r,char ch) {
int lef=1,rig=n,ret=0;
while(lef<=rig) {
int mid=lef+rig>>1; bool judge;
if(ch=='p') judge=solver.gethash(l,l+mid-1)==solver.gethash(r,r+mid-1);
else judge=solver.gethash(l-mid+1,l)==solver.gethash(r-mid+1,r);
if(judge) ret=mid,lef=mid+1;
else rig=mid-1;
}
return ret;
}
long long f1[maxn],f2[maxn];
char buf[maxn];
void solve() {
memset(f1,0,sizeof f1);
memset(f2,0,sizeof f2);
solver.clearall();
scanf("%s",buf+1);
n=strlen(buf+1);
solver.init(buf);
for(int len=1;len<=n;len++) {
for(int j=len+len;j<=n;j+=len) {
int lcs=Q(j-len,j,'s'),lcp=Q(j-len,j,'p'),l,r;
l=std::max(j,j-len-lcs+len+len),r=std::min({n,j+len-1,j+lcp-1});//特别要注意,这里合法的区间不能包含2个以上标记的点,也不能超过n
if(l>r) continue;//无解
f1[l]++; f1[r+1]--;//区间[l,r]加上1
int delta=r-l; l=std::max(j-len-len+1,j-len-lcs+1),r=l+delta;//这里直接加上一次区间的长度即可,省去一次麻烦的计算
f2[l]++; f2[r+1]--;
}
}
long long ans=0;
for(int i=1;i<=n;i++) f1[i]+=f1[i-1],f2[i]+=f2[i-1];
for(int i=1;i<n;i++) ans+=f1[i]*f2[i+1];
printf("%lld\n",ans);
}
int main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话