CF1270F Awesome Substrings
Question 问题 CF1270F Awesome Substrings
基本信息
知识点:根号分块
题目大意:
- 给定一个长度为 \(n\) 的 01 串 \(s\)。
- 求有多少个区间 \([l,r]\) 满足 \(r-l+1\) 是 \(s_{l...r}\) 中 \(1\) 的个数的倍数。
Solution 根号分治
根据套路,我们将这个问题分为两块来处理,分别是 \(k \le \sqrt{n}\) 和 \(k > \sqrt{n}\) 。
我们令 \(cnt_{num}\) 代表一段区间内 \(num\) 的个数,\(pos_i\) 为第 \(i_{th}~1\) 的下标 。
枚举倍数 \(k\) 使得 \(k \times cnt_1 = cnt_1 + cnt_0\)
- \(k \le \sqrt{n}\)
问题可以看作已知倍数 \(k\) 求满足 \(k \times cnt_1 = cnt_1 + cnt_0\) 的区间个数。这种问题有一种方法就是给两个数赋值。
我们计算一下这个式子,\((k-1) \times cnt_1 - cnt_0 = 0\),相当于给 \(1\) 赋值为 \(k-1\),\(0\) 赋值为 \(-1\),在满足上述式子的前提下怎样分配权值都是可以的。
所以做一个前缀和,开个桶记录合法情况个数。(unordered_map
好像会超时,直接用数组,数据范围不大)
- \(k > \sqrt{n}\)
此时 \(1\) 的个数很少,上界为 \(\sqrt{n}\)。考虑直接枚举 \(1\) 的个数 \(cnt\),再枚举一个左端点 \(l\),右端点 \(r\) 的范围就可以通过 \(1\) 的位置夹住算出来,令范围为 \([r_l,r_r]\),贡献为 \(\lfloor \frac{r_r-l+1}{cnt} \rfloor-\operatorname{max}(\sqrt n,\lfloor \frac{r_l-l}{cnt} \rfloor)\)。记住取 \(\operatorname{max}\) 的原因是此时我们讨论的是 \(k > \sqrt n\) 的情况,\(k \le \sqrt 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;
}