题解:CF1984D "a" String Problem
CF1984D 题解
题面
题意
(翻译没给得很清楚,一开始我还差点错了。)
给定一个字符串 \(s\),求有多少个子串 \(t\) 满足:
- \(t\neq\)
a
。 - \(s\) 可以由若干个 \(t\) 和若干个
a
组成。 - 组成 \(s\) 的方法至少要有一个 \(t\)。
数据范围:\(2\leqslant|s|\leqslant2\times10^5\)
思路
观察题目可以敏锐的感觉到 a
是本题中的关键。
首先先考虑特殊情况,全是 a
的情况,这时候由于题目有给 \(t\neq\) 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\vert cnt\),于是就可以考虑暴力在 \(c\) 中判断 \(cnt\) 的因数长度的是否可以形成循环节(包括判断其中的 a
),然后可以的话累加答案即可。
时间复杂度 \(O(n\sqrt n)\),可以过。
实现方法
上面思路是讲完了,相信一些高手已经自己尝试做了,接下来就让我讲下我当时想到上面方法后的解法。
首先先不看一个子串前面的 a
和后面的 a
,将 \(c\) 提取出来后直接开始枚举 \(cnt\) 的每一个因数。
对于每一个因数,需要判断 \(c\) 能否分成 \(\frac{cnt}{i}\) 个长度为 \(i\) 的相同的子串(要考虑上中间插这的原串的 a
),其实这不难,只要一个个枚举 \(c\) 中的每个子串,再判断每个相邻的子串中的字符,以及子串中每个字符间 a
的个数是否相同即可,至于 a
的个数,只要两个非 a
字符的位置相减再减一就可以得到,而每个非 a
的字符也处理好了,所以这很轻松就可以判断,时间复杂度为 \(O(\frac{cnt}{i}\times i)=O(cnt)\),这是加上枚举因数的 \(O(\sqrt 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;
}