这道题题意易理解,求给定两个数a,b之间的round numbers,所谓round numbers就是这个数转化成二进制表示后其中0的个数要大于1的个数就是round numbers,这道题开始做的时候跪了一天,但仔细想想也不是很难。
要求出数a与数b之间的round numbers,可以先算出1到a之间的round(代指round numbers),再求出1到b之间的round,再相减即可,所以问题转移成求1到一个数n之间的round。
要求1到任意一个数n之间的round,我们的思路如下:
①先将n转化成二进制表示,然后求出其长度L,这样比n小的round就分为长度比L小的和长度等于L的。
②先求出长度比L小的round,我用自己定义的一个total函数计算的,for循环令i从1开始到L-1,依次计算total(i),在解释total()函数如何实现之前,先想一个问题,对于给定其二进制数中1的个数n和0的个数m,求其有多少种可能的情况,首先首位必须放一个1,不然就不是一个数字,那么整个数的位数就减1,也就变成(m+n-1)为,1的个数变成(n-1)位,m的个数仍旧m位,这样1和0就可以随便排列了,但要保证不重复,借助排列组合的知识,我们可以知道首先假定这些数字全都不一样,然后其全排列为(m+n-1)!,又由于它们之间1是一样的,0也是一样的,所以要除以它们内部的全排列,即最后结果为(m+n-1)!/(m!*(n-1)!),这时候,你注意到了吗?这个式子其实是从m+n-1项中选m项做组合,即C(m+n-1,m)。所以total函数的实现就是在满足0的个数大于1的个数的情况下用组合数求其所有的可能结果。
③然后是求长度等于L的round,因为要求的round要比数n要小,所以我们这样思考,比如一个二进制数100010101000,我用红颜色标记的1,它的权重是比其右边所有数的权重加起来要大的,所以如果将其改成0,那么其右边的任何数改成任意值都会比原先的值小,所以这个问题又解决了,只要我们从最左边搜索1,每搜到1就可以假定其为0这样,其位数左边的值就可以随便改了,但要保证其0的总数大于1的总数。那该如何保证呢?我在代码中设置了zeros和ones分别标记从左开始已经记录的0的个数和1的个数,假设剩余的长度中有a个1和b个0,那么
zeros + ones + a + b = L ⑴
b + zeros >= a + ones ⑵
所以由⑴和⑵可得b >= (L - 2*zeros)/2.
这里得到的是0的个数的下界,上界就是剩余的位置全部置0,即L - zeros - ones。但是还没完,L - 2*zeros < 0时,就会增加错误的结果,你仔细想想就会知道当前面0的数量过多的时候后面全部放1都能使这个数为round,但for循环却会从负数开始加起,这就导致结果错误,所以需要加一个判断条件当为负数时,令其为零。然后在满足round条件的情况下利用上述的组合公式求出所有符合条件的情况。
④组合数求法,用的是init函数初始化C[][]的数组,C[n][m]表示从m中选n做组合。若直接用组合数公式求,中间数据必然会超出范围,那么此处的解决方法是利用杨辉三角,由杨辉三角可得到这样一个组合数公式,
C(n,m)=C(n-1,m-1)+C(n-1,m);
所以组合数的求法就可以用递推解决。
这样,求得两个数的从一到该数的round,然后相减就得到结果了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int C[35][35]={0};
void init()
{
for(int i=0;i<=32;i++)
for(int j=0;j<=i;j++)
if(!j||i==j)
C[i][j]=1;
else
C[i][j]=C[i-1][j-1]+C[i-1][j];
return ;
}
int total(int l)
{
int sum=0;
for(int i=0;i<(l-1)/2;i++)
sum+=C[l-1][i];
if((l-1)%2!=0)
sum+=C[l-1][(l-1)/2];
return sum;
}
int Sum(int x,int flag)
{
int y=x;
int a[35]={0};
int l=0;
for(int i=0;x!=0;i++)
{
a[i]=x%2;
x/=2;
l++;
}
for(int i=0;i<l/2;i++)
{
int temp=a[i];
a[i]=a[l-i-1];
a[l-i-1]=temp;
}//得到x的二进制表示
int w=0;//w用于统计总数
for(int i=1;i<l;i++)
w+=total(i);//得到从1到l之间的round.
if(flag)//如果是第二个数据,要判断其是否为round
{
int zeros=0, ones=0;
for(int i=0;i<l;i++)
if(a[i]==0)
zeros++;
else
ones++;
if(zeros>=ones)w++;
}
int zeros=0;
int ones=1;
for(int i=1;i<l;i++)//计算等长的数中比给定值小的round;
{
zeros++;
if(a[i]==1)
{
int m=l-2*zeros;
if(m>=0)
{
if(m%2!=0)
m=m/2+1;
else
m=m/2;
}
else
m=0;
for(int j=m;j<=l-zeros-ones;j++)
w+=C[l-zeros-ones][j];
ones++;
zeros--;
}
}
return w;
}
int main()
{
int a, b;
init();
scanf("%d%d",&a,&b);
int x=Sum(a,0);
int y=Sum(b,1);
printf("%d\n",y-x);
return 0;
}