http://poj.org/problem?id=3252
题意简述:输入的两个整数(包括)之间所有整数,转换为二进制后,0的位数大于等于1的位数的数字个数。
首先要求 l-r 中的数字个数,自然会想到求 [1->r]-[1->(l-1)];
但是要求的是转化为二进制后0的位数大于等于1的位数的数字个数,我们每次求的都是小于r或者l的数,
所以区间变为(1->r+1)-(1->l); 对于一个数x,它的二进制的位数为i 我们可以分两种情况:
1.如果一个数的二进制位数小于x的二进制位数,显然它是小于x的,而要使0的位数>=1的位数,就得使0的个数>=(i+1)/2, 我们可以预处理出所有数的二进制下一共i位,有>=j位是0的数的个数。然后从第一位枚举到i-1位,求出∑d[i-1,(i+1)/2]。2.如果一个数的二进制位数等于i,那么需要逐位验证当前位是i还是0,统计出之前验证的0的个数,每次确定0的个数时要减去 前边的。
举个例子:
出自:
http://www.cnblogs.com/kuangbin/archive/2012/08/22/2651730.html
22=10110
如果要求 <=22的Round Numbers,也就是找出1-22有多少个二进制的0不少于1的数的个数。
22的二进制长度是5.
首先找长度比5小的Round Numbers(长度比5小的数肯定小于22啦)
长度为4的话,第一位必须是1,后面三位的话,可以有2个0,3个0
所以就是C(3,2)+C(3,3);
长度为3的Round Numbers,同理有 C(2,2);//注意不要把第一位1忘记了
长度为2的Round Numbers,有C(1,1)
长度为1的Round Numbers,有 0个
下面是找长度和22相同的Round Numbers。
首先第一位是1.
22的第二位是0,所以第二位不能为1,必须是0
第三位为0的话,(前面有了2个0,1个1),后面两位可以有1个0,2个0
C(2,1)+C(2,2)
接下来把第三位恢复为1,看第四位.假如第四位是0(前面有2个0,2个1),后面一位必须是0,所以是C(1,1)
#include<iostream>
#include<cstdio>
using namespace std;
int ans,k,s,e,a,b;
int f[35][35],d[35][35];
int work(int x,int bit){//x 当前数,bit位数
int sum=0;
for (int i=1;i<bit;i++) sum+=d[i-1][(i+1)>>1];//二进制位数小于bit位,则一定是小于当前值x的
k=0;
for (int i=bit-2;i>=0;i--)//向右移动i位可以验证第i+1位是0 or 1
if ((x>>i)&1){
ans=((bit+1)>>1)-k-1;//剩下的0的个数
if (ans<0) ans=0;
sum+=d[i][ans];
}
else k++;// k是0的个数
return sum;
}
int main(){
for (int i=0;i<=31;i++) f[i][0]=1;
for (int i=1;i<=31;i++)
for (int j=1;j<=i;j++)
f[i][j]=f[i-1][j]+f[i-1][j-1];//求组合数
d[0][0]=1;
for (int i=1;i<=31;i++)
for (int j=i;j>=0;j--)
d[i][j]=d[i][j+1]+f[i][j];
cin>>s>>e;
e++;
while(s>>a) a++;//s 的二进制位数
while(e>>b) b++;
cout<<work(e,b)-work(s,a);
return 0;
}
f[i][j]存储组合数
d[i][j]表示二进制下一共i位,有>=j位是0的数的个数。
求区间[s,e]的RoundNumber可以转化[1,e+1)-[1,s) 设某数s在二进制下有a位,则1~a-1位的数很容易为:
∑d[i-1,(i+1)/2](1<=i<=a-1),其中第一位必须是1。 下面就是计算a位中比s小的符合要求的数:
若s上某一位是1(除了第一位),这一位前面已经有k个0,后边还有j位, 可以把这一位变成0,这样前面有k+1个0,
后边需要的0的个数至少为j=max(0,(a+1)/2-k-1),所以sum+=d[i][j]。