题解:CF1984D "a" String Problem
CF1984D 题解
题面
题意
(翻译没给得很清楚,一开始我还差点错了。)
给定一个字符串 s,求有多少个子串 t 满足:
- t≠
a
。 - s 可以由若干个 t 和若干个
a
组成。 - 组成 s 的方法至少要有一个 t。
数据范围:2⩽|s|⩽2×105
思路
观察题目可以敏锐的感觉到 a
是本题中的关键。
首先先考虑特殊情况,全是 a
的情况,这时候由于题目有给 t≠ a
,所以答案就为 |s|−1,t 可以是除了 a
以外的所有字符串。
接下来加入非 a
字符。由于 s 是要可以由若干个 t 和若干个 a
组成,且至少要有一个 t,那么就影响到 t 要包含所有的非 a
字符,且包含得都得一样。
考虑可以记录每一个非 a
字母再远字符串中的位置,这样就可以根据相邻两个非 a
字母的位置的差值知道 a
的分布情况,在此基础上,我们还可以把非 a
字母提取出来作为 c,长度为 cnt。
拿着有什么用呢?观察发现,对于每一种有 k 个非 a
字符的子串 t,由于若干个 t 和 a
拼凑可以拼成 s,所以 t 里得有等量的非 a
字符,并且每个非 a
字符出现的相对位置一样,即要是不考虑 t 中的 a
,则 k|cnt,于是就可以考虑暴力在 c 中判断 cnt 的因数长度的是否可以形成循环节(包括判断其中的 a
),然后可以的话累加答案即可。
时间复杂度 O(n√n),可以过。
实现方法
上面思路是讲完了,相信一些高手已经自己尝试做了,接下来就让我讲下我当时想到上面方法后的解法。
首先先不看一个子串前面的 a
和后面的 a
,将 c 提取出来后直接开始枚举 cnt 的每一个因数。
对于每一个因数,需要判断 c 能否分成 cnti 个长度为 i 的相同的子串(要考虑上中间插这的原串的 a
),其实这不难,只要一个个枚举 c 中的每个子串,再判断每个相邻的子串中的字符,以及子串中每个字符间 a
的个数是否相同即可,至于 a
的个数,只要两个非 a
字符的位置相减再减一就可以得到,而每个非 a
的字符也处理好了,所以这很轻松就可以判断,时间复杂度为 O(cnti×i)=O(cnt),这是加上枚举因数的 O(√n) 是可以接受的。
接下来就是统计答案,这就要考虑上每个提取出来的串前面的和后面的 a
了,首先我们知道对于没有前后的 a
两个子串之间的 a
是共用的(就是要想在前后各加一个 a
,则至少每个子串之间要有 2 个 a
),所以我们只要找出子串之间最少的 a 的个数,统计答案即可,具体以可以根据代码理解,代码中我枚举了后一个子串前面可以带上 a
的个数,再加上对应情况前一个子串后面可以带上 a
的个数的种数,注意要考虑可以不带 a
。
于是就写好了,具体看代码吧!
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=200005;
ll T,len,cnt,p[MN],ans,b,e,minn;
//len是s的长度,cnt是非a字符的个数,b是第一个非a字符前面的a的个数,e是最后一个非a字符后面的个数,p是统计每个非a字符在原串中的位置
char s[MN],c[MN];
bool flag,ok;
void write(ll n){if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
void work(ll k){
ok=false;minn=2e5;//初始化,由于len<=2e5,所以初始时直接设为2e5
for(ll i=2*k; i<=cnt; i+=k) if(!ok) for(int j=i-2*k+1; j<=i-k; j++) if(c[j]!=c[j+k]||(j!=i-2*k+1&&p[j]-p[j-1]!=p[j+k]-p[j+k-1]/*要考虑出第一个以外每个字符中间a的个数是否相同*/)){ok=true;break;}//判断当前长度的子串是否是一个循环节,即每个子串出现的相对顺序是否一样
if(!ok){
b=p[1]-1;e=len-p[cnt];
for(ll i=k+1; i<=cnt; i+=k) minn=min(minn,p[i]-p[i-1]-1);//统计各个子串中中间包含的a最少的个数
if(b+e<=minn) ans+=(b+1)*(e+1);//直接乘法原理,记得有不带a的情况
else for(ll i=0; i<=min(b,minn); i++/*枚举后一个字符串前面可以占几个a*/) ans+=min(minn-i,e)+1;//要加上空(不带前面的或者后面的a)的情况
}
}
int main(){
T=read();while(T--){
flag=false;cnt=0;ans=0;//初始化
scanf("%s",s+1);len=strlen(s+1);
for(ll i=1; i<=len; i++) if(s[i]!='a') flag=true,p[++cnt]=i,c[cnt]=s[i];
if(!flag){write(len-1);putchar('\n');continue;}//特判,全是a
for(ll i=1; i*i<=cnt; i++) if(cnt%i==0&&i*i!=cnt) work(i),work(cnt/i);else if(i*i==cnt) work(i);//只要枚举因子即可,注意要特殊处理平方根。
write(ans);putchar('\n');//记得换行
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现