题目大意是:给你两个数n和m,计算从n到m的所有数的二进制中,0的个数大于1的个数的数的个数。
这题用组合数学比较简单,不过要小心,很容易超时的,我就悲哀的RE了两次。
对于一个长度为Len的二进制(最高位为1),如何求出他的RoundNumbers呢(假设为用R(len)来表达),分为奇数和偶数两种情况
1、奇数情况:在Len=2k+1的情况下,最高位为1,剩下2k位,至少需要k+1为0
用C(m,n)表示排列组合数:从m个位置选出n个位置的方法
R(len)=C(2k,k+1)+C(2k,k+2)+...+C(2k,2k).
由于 A:C(2k,0)+C(2k,1)+...+C(2k,2k)=2^(2k)
B:C(2k,0)=C(2k,2k), C(2k,1)=C(2k,2k-1) ,,C(2k,i)=C(2k,2k-i)
于是 C(2k,0)+C(2k,1)+...+C(2k,2k)
= C(2k,0)+C(2k,1)+...+C(2k,k)+C(2k,k+1)+C(2k,K+2)+...+C(2k,2k)
= 2*R(len)+C(2k,k)
=2^(2k)
所以R(len)=1/2*{2^(2k)-C(2k,k)};
2. 偶数情况 len=2*k,类似可以推到 R(len)=1/2*(2^(2k-1));
然后输入n和m,用cal(m)-cal(n-1)即可;
代码如下:
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> using namespace std; int p[35],st[35]; int n,m; int f(int n,int m) { int i,j,s; if(m>n-m)m=n-m; s=1;j=1; for(i=n-m+1;i<=n;i++) { s*=i; while(j<=m&&s%j==0) { s=s/j; j++; } } return s; } void init() { int i,j; memset(p,0,sizeof(p)); p[1]=0; for (i=2;i<32;i++) for(j=1;j<=i-j;j++) p[i]+=f(i-1,j-1); } int cal(int n) { if(n<2) return 0; int i,j,s,x; x=n; int len=1; while(x) { x=x/2; len++; } len--; for(i=len;i>0;i--) { st[i]=n%2; n=n/2; } /*for(i=1;i<=len;i++) printf("%d ",st[i]); printf("\n");*/ s=1; int s1=1; int s0=0; for(i=1;i<len;i++) s+=p[i]; for(i=2;i<=len;i++) if(st[i]) { if(s0+len-i>=s1+1)s++; for(j=1;j<=len-i&&s1+j<=len-(s1+j);j++) s+=f(len-i,j); s1++; } else s0++; return s; } int main() { init(); cin>>n>>m; cout<<cal(m)-cal(n-1)<<endl; return 0; }