Question 问题 CF1270F Awesome Substrings
基本信息
难度: 省选/NOI- \colorbox{#9d3dcf}{\color{White}省选/\text{NOI-}} 省选/NOI-
知识点:根号分块
题目大意:
- 给定一个长度为 n n n 的 01 串 s s s。
- 求有多少个区间 [ l , r ] [l,r] [l,r] 满足 r − l + 1 r-l+1 r−l+1 是 s l . . . r s_{l...r} sl...r 中 1 1 1 的个数的倍数。
Solution 根号分治
根据套路,我们将这个问题分为两块来处理,分别是 k ≤ n k \le \sqrt{n} k≤n 和 k > n k > \sqrt{n} k>n 。
我们令 c n t n u m cnt_{num} cntnum 代表一段区间内 n u m num num 的个数, p o s i pos_i posi 为第 i t h 1 i_{th}~1 ith 1 的下标 。
枚举倍数 k k k 使得 k × c n t 1 = c n t 1 + c n t 0 k \times cnt_1 = cnt_1 + cnt_0 k×cnt1=cnt1+cnt0
- k ≤ n k \le \sqrt{n} k≤n
问题可以看作已知倍数 k k k 求满足 k × c n t 1 = c n t 1 + c n t 0 k \times cnt_1 = cnt_1 + cnt_0 k×cnt1=cnt1+cnt0 的区间个数。这种问题有一种方法就是给两个数赋值。
我们计算一下这个式子, ( k − 1 ) × c n t 1 − c n t 0 = 0 (k-1) \times cnt_1 - cnt_0 = 0 (k−1)×cnt1−cnt0=0,相当于给 1 1 1 赋值为 k − 1 k-1 k−1, 0 0 0 赋值为 − 1 -1 −1,在满足上述式子的前提下怎样分配权值都是可以的。
所以做一个前缀和,开个桶记录合法情况个数。(unordered_map
好像会超时,直接用数组,数据范围不大)
- k > n k > \sqrt{n} k>n
此时 1 1 1 的个数很少,上界为 n \sqrt{n} n。考虑直接枚举 1 1 1 的个数 c n t cnt cnt,再枚举一个左端点 l l l,右端点 r r r 的范围就可以通过 1 1 1 的位置夹住算出来,令范围为 [ r l , r r ] [r_l,r_r] [rl,rr],贡献为 ⌊ r r − l + 1 c n t ⌋ − max ( n , ⌊ r l − l c n t ⌋ ) \lfloor \frac{r_r-l+1}{cnt} \rfloor-\operatorname{max}(\sqrt n,\lfloor \frac{r_l-l}{cnt} \rfloor) ⌊cntrr−l+1⌋−max(n,⌊cntrl−l⌋)。记住取 max \operatorname{max} max 的原因是此时我们讨论的是 k > n k > \sqrt n k>n 的情况, k ≤ n k \le \sqrt n k≤n 的情况要舍掉。
Code 代码
string s,t;
int n,blo,sum[N],pos[N],cnt[500*N];//桶的范围要开大一点
ll ans;
int main(){
cin>>t;s+=" ";s+=t;n=s.size()-1;//下标从 1 开始
blo=sqrt(n);//块长,我们以根号n分类讨论
for(rint k=1;k<=blo;k++){
cnt[n]=1;
for(rint i=1;i<=n;i++){
sum[i]=sum[i-1]+(s[i]=='1'?k-1:-1);//赋值
ans+=cnt[sum[i]+n];
cnt[sum[i]+n]++;
}
for(rint i=1;i<=n;i++) cnt[sum[i]+n]--;
for(rint i=1;i<=n;i++) sum[i]=0;
}
for(rint i=1;i<=n;i++){
if(s[i]=='1') sum[i]=sum[i-1]+1,pos[sum[i]]=i;
else sum[i]=sum[i-1];
}//预处理前缀和 和 1 的下标
pos[sum[n]+1]=n+1;//设个右端点防止越界问题
for(rint l=1;l<=n;l++){//枚举左端点
for(rint i=1;i<=n/blo;i++){//枚举 1 的个数
int rl=pos[i+sum[l-1]],rr=pos[i+sum[l-1]+1]-1,res=(rr-l+1)/i-max(blo,(rl-l)/i);
if(rl>0&&rr>0&&res>0) ans+=res;
}
}
cout<<ans<<endl;
return 0;
}