Description
The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro, Sham, Bo', and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can't even flip a coin because it's so hard to toss using hooves.
They have thus resorted to "round number" matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both "round numbers", the first cow wins,
otherwise the second cow wins.
A positive integer N is said to be a "round number" if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.
Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many "round numbers" are in a given range.
Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).
Input
Output
Sample Input
2 12
Sample Output
6
题目大意
给定一个闭区间,求区间内有多少个数满足转为二进制表示后0的个数大于等于1的个数
1 ≤ Start < Finish ≤ 2*e9
思路1 数位DP
dp[k][i][j] k为长度,i为0的个数,j为1的个数
考虑添加“first”变量,判断当前是不是首位(因为我们枚举是不问一个数的真实位数(有前导0),即每个数的位数是一样的,只不过在前面加0,那当然这些0是不能计入的)。如果传进来的first=1并且下一位枚举i=0,则传下去的first依然是1,即还没有到这个数“真正”的部分。(一个数转为二进制真正的部分永远以1开头)显然一旦first变为0后,后面将不变。
对于num0和num1的传参:
如果还没到首位(first还是1)的话,则直接传入num0、num1为0;如果first为0了,则传入num0+(i== 0)
和num1+(i==1)。
代码示例
//#define LOCAL
#include
#include
#include
#include
#include
#include
using namespace std;
int m,n;
int dp[50][50][50];
int bit[50];
//dp[k][i][j] k为长度,i为0的个数,j为1的个数
int dfs(int pos,int num0,int num1,int first,int limit)
{
//num0,num1分别为0,1的个数,first表示第一个1的位置
if(pos<0) return num0>=num1;//套路,num0>=num1符合题意
if(!limit&&dp[pos][num0][num1]!=-1) return dp[pos][num0][num1];
int ans=0;
int up=limit?bit[pos]:1;//注意只有0,1
for(int i=0;i<=up;++i){
int t=first&&(i==0);//当first为1并且i=0时,表示第一位1还未放,下一位依然是“首位”num0=num1=0
//当t为0,则根据枚举(i==0)来确定放0还是10
ans+=dfs(pos-1,t?0:num0+(i==0),t?0:num1+(i==1),t,limit&&i==up);
}
if(!limit) dp[pos][num0][num1]=ans;
return ans;
}
int solve(int n)//存储二进制位
{
int len=0;
while(n)
{
bit[len++]=n%2;
n>>=1;
}
return dfs(len-1,0,0,1,1);
}
int main()
{
//std::ios::sync_with_stdio(false);
#ifdef LOCAL
freopen("read.txt","r",stdin);
#endif
memset(dp,-1,sizeof(dp));
while(cin>>m>>n)
{
printf("%d\n",solve(n)-solve(m-1));
}
return 0;
}
思路2 组合数
考虑求解1~数m中有多少符合题意解,最后output : solve(m)-solve(n-1)
分两类
1.二进制位数较小
一定满足位数小的数值小,所以都能取到。直接用组合数计算
例如33=100001 二进制6位 考虑1~5位的情况 这时候每一位首一定是1
5位:第一位一定为1 后面4位 C(4,3)+C(4,4)
4位:第一位一定为1 后面3位 C(3,2)+C(3,3)
......
......
......(可类比)
公式推导:
观察可知后面的选择与长度的奇偶性有关
当len-1(这个-1是因为确定了第一位为1)为偶数:
C(len-1, (len-1)/2+1 ) + C(len-1, (len-1)/2+2 ) +......+ C(len-1,len-1) =( 2^(len-1) - C(len-1,(len-1)/2) )/2
证明:即证C(a,a/2+1)+C(a,a/2+2)+......+C(a,a)=( 2^a-C(a,a/2) ) /2
因为C(a,0)+C(a,1)+C(a,2)+......+C(a,a/2)+......+C(a,a-1)+C(a,a)=2^a
且前后蓝色两部分相等 移项得证
当len-1(这个-1是因为确定了第一位为1)为奇数:
C(len-1,len/2)+C(len-1,len/2+1)+......+C(len-1,len-1)=( 2^(len-1) )/2
证明同理
2.二进制位数相同
位数相同时,采用的是从高位向低位处理,如果该位是0,continue
如果该位是1,将其视为0,并计算后面低位满足题意的数量(这时候高位1变0了,一定在范围内)
具体可以参考代码注释
代码示例
#include
#include
#include
#include
#include
#include
using namespace std;
int c[35][35];
void init()//组合数
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<35;++i){
c[i][0]=1;
for(int j=1;j0)//注意高位在数组后部
{
if(n&1) bit[len++]=1;
else bit[len++]=0;
//cout<>=1;
}
//cout<0;i--){//计算位数小的
if(i%2==0) ans+=((1<<(i-1))/2);
else ans+=((1<<(i-1))-c[i-1][(i-1)/2])/2;
}
//cout<=cnt1) ans++;
cnt0=0;
cnt1=1;
for(int i=len-2;i>=0;--i){
if(bit[i]==1){//按照0考虑 因为按照1考虑时,后面可能会有大的数
//另外按照0考虑的数量包含按照1考虑的数量,因为是1都满足0>=1,是0也一定满足
for(int j=i;j>=0&&j+cnt0+1>=i-j+cnt1;--j)//j表示有多少位为0
ans+=c[i][j];
cnt1++;
}
else cnt0++;
}
return ans;
}
int main()
{
int m,n;
init();
while(cin>>n>>m)
{
cout<
由本题可以看出,数位DP像是组合数的升级版,两者都有递推的思想(组合数init时采用的递推式,数位DP记忆化搜索)
但数位DP可以解决那些看起来不这么规律的问题,限制条件比较多样
而本题组合数是可以直接计算的