题解:CF1984D "a" String Problem

CF1984D 题解

题面

原题传送门

题意

(翻译没给得很清楚,一开始我还差点错了。)

给定一个字符串 s,求有多少个子串 t 满足:

  1. t a
  2. s 可以由若干个 t 和若干个 a 组成。
  3. 组成 s 的方法至少要有一个 t

数据范围:2|s|2×105

思路

观察题目可以敏锐的感觉到 a 是本题中的关键。

首先先考虑特殊情况,全是 a 的情况,这时候由于题目有给 t a,所以答案就为 |s|1t 可以是除了 a 以外的所有字符串。

接下来加入非 a 字符。由于 s 是要可以由若干个 t 和若干个 a 组成,且至少要有一个 t,那么就影响到 t 要包含所有的非 a 字符,且包含得都得一样。

考虑可以记录每一个非 a 字母再远字符串中的位置,这样就可以根据相邻两个非 a 字母的位置的差值知道 a 的分布情况,在此基础上,我们还可以把非 a 字母提取出来作为 c,长度为 cnt

拿着有什么用呢?观察发现,对于每一种有 k 个非 a 字符的子串 t,由于若干个 ta 拼凑可以拼成 s,所以 t 里得有等量的非 a 字符,并且每个非 a 字符出现的相对位置一样,即要是不考虑 t 中的 a,则 k|cnt,于是就可以考虑暴力在 c 中判断 cnt 的因数长度的是否可以形成循环节(包括判断其中的 a),然后可以的话累加答案即可。

时间复杂度 O(nn),可以过。

实现方法

上面思路是讲完了,相信一些高手已经自己尝试做了,接下来就让我讲下我当时想到上面方法后的解法。

首先先不看一个子串前面的 a 和后面的 a,将 c 提取出来后直接开始枚举 cnt 的每一个因数。

对于每一个因数,需要判断 c 能否分成 cnti 个长度为 i 的相同的子串(要考虑上中间插这的原串的 a),其实这不难,只要一个个枚举 c 中的每个子串,再判断每个相邻的子串中的字符,以及子串中每个字符间 a 的个数是否相同即可,至于 a 的个数,只要两个非 a 字符的位置相减再减一就可以得到,而每个非 a 的字符也处理好了,所以这很轻松就可以判断,时间复杂度为 O(cnti×i)=O(cnt),这是加上枚举因数的 O(n) 是可以接受的。

接下来就是统计答案,这就要考虑上每个提取出来的串前面的和后面的 a 了,首先我们知道对于没有前后的 a 两个子串之间的 a 是共用的(就是要想在前后各加一个 a,则至少每个子串之间要有 2a),所以我们只要找出子串之间最少的 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;
} 
posted @   naroto2022  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
花开如火,也如寂寞。