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\)

  1. \(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 好像会超时,直接用数组,数据范围不大)

  1. \(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;
}
posted @ 2024-05-29 21:54  Mr_Azz  阅读(2)  评论(0编辑  收藏  举报