【组合数学】 Round Numbers

题目传送门

题目描述:

给定一个区间[l,r],问l到r的整数中有几个转换成二进制数后0比1多(不计前导零)。


Solution

  • 首先,我们可以引出一个性质:
    对于任何一个二进制数n,将它各个位数之间的一变成0所组成的若干个区间恰好包含 [ 0 , n ] [0,n] [0,n]这个区间

例如对于整数11,它二进制拆分后是 1011 1011 1011
按照上述过程进行拆分能得到多个不同的二进制数:
1010 ( 10 ) 1010(10) 1010(10) , 1000 ( 8 ) 1000(8) 1000(8) , 0000 ( 0 ) 0000(0) 0000(0)
它们互相组合,可以得到整个区间:
[ 0000 , 1000 ) [0000,1000) [0000,1000), [ 1000 , 1010 ) [1000,1010) [1000,1010)
以及1011

可以发现这样就可以把 1 − n 1-n 1n之间的数全都包含,这样就可以用类似前缀和的形式求出答案:
w o r k ( r + 1 ) − w o r k ( l ) work(r+1)-work(l) work(r+1)work(l)
其中 r + 1 r+1 r+1, l l l是因为work求的是比work()中的数小的数,是因为我们没有把最后那个落单的整数算上去,只算到了 [ 1 , n − 1 ] [1,n-1] [1,n1]区间。

我们将二进制处理出来之后,我们就可以枚举每一位,如果当前位是0,那么就把0的总数加1,否则就将答案累加。

我们预处理出组合数 C i j C_i^j Cij表示在i个可以选的位置中插入j个0的合法方案数

  • 如何累加呢?
    想要方案是合法的,0就必须大于等于 n + 1 2 \frac {n+1}2 2n+1,这个是很想然的

    当查询到一个1的时候,我们已知当前0的个数为 z n zn zn个,当前位置为i

    想要使方案合法,就至少添加 n + 1 2 − z n − 1 \frac {n+1}2 - zn-1 2n+1zn1个0(减1是因为我们把当前1搞成了0),而上限则是i,说明全部都为0,添加上组合数即可

设二进制长度为len
而上述过程计算的是长度等于len的方案数。
由于这里是计算 [ 1 , n − 1 ] [1,n-1] [1,n1]之间所有的方案数,所以我们还要计算长度小于len的方案数。

双重循环累加即可,过程与上述相似。


Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<stack>
#include<set>
#include<map>
#include<queue>
using namespace std;
int L,R,len = 0;
int c[6060][6060];
int a[1010100];

void init(){
	for (int i=0;i<50;i++)
	  for (int j=0;j<=i;j++)
	    c[i][j] = i==j||j==0?1:c[i-1][j-1]+c[i-1][j];
}//预处理组合数

void cl(int x){
	while (x){
		a[++a[0]] = x&1?1:0;
		x>>=1;
		cnt++;
	}
}//二进制拆分处理

int work(int x){
	a[0] = 0;
	int ans = 0;
	len = 0;
	cl(x);
	for (int i=1;i<a[0]-1;i++)
	  for (int j=i/2+1;j<=i;j++)
	    ans += c[i][j];//长度小于len(a[0])
	int zn = 0;//0的个数
	for (int i = a[0]-1;i>=1;i--)
	  if (a[i])
	    for (int j=(a[0]+1)/2-zn-1;j<i;j++)
	      ans += c[i-1][j];//同上述过程,改成0的方案数
	  else zn++;//0的个数++
	return ans;
}

signed main(){
	init();
	while (~scanf("%d %d",&L,&R)){
		int sum = work(R+1) - work(L);
		printf("%d\n",sum);
	}
}

//ps:tm的不知道为什么,这题开long long会炸,大家千万不能开long long!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值