CF1270F Awesome Substrings

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 rl+1 s l . . . r s_{l...r} sl...r 1 1 1 的个数的倍数。

Solution 根号分治

根据套路,我们将这个问题分为两块来处理,分别是 k ≤ n k \le \sqrt{n} kn 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

  1. k ≤ n k \le \sqrt{n} kn

问题可以看作已知倍数 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 (k1)×cnt1cnt0=0,相当于给 1 1 1 赋值为 k − 1 k-1 k1 0 0 0 赋值为 − 1 -1 1,在满足上述式子的前提下怎样分配权值都是可以的。

所以做一个前缀和,开个桶记录合法情况个数。(unordered_map 好像会超时,直接用数组,数据范围不大)

  1. 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) cntrrl+1max(n ,cntrll⌋)。记住取 max ⁡ \operatorname{max} max 的原因是此时我们讨论的是 k > n k > \sqrt n k>n 的情况, k ≤ n k \le \sqrt n kn 的情况要舍掉。

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;
}
  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值