题目问区间有多少个数字的二进制0的个数大于等于1的个数。
用数学方法求出0到n区间的合法个数,然后用类似数位DP的统计思想。
我大概是这么求的,确定前缀的0和1,然后后面就是若干个0和若干个1的不重复全排列数。。
写得挺痛苦的。。另外A[i][j]表示i个0和j个1的不重复全排列数,即A[i][j]=(i+j)!/i!/j!,这个可以从A[i-1][j]或A[i][j-1]求出来,这样就不用担心乘法溢出了。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 long long d[33][33]; 5 long long cnt(int zero,int one,int len){ 6 long long res=0; 7 for(int i=0; i<=len; ++i){ 8 int j=len-i; 9 if(i+zero<j+one) continue; 10 res+=d[i][j]; 11 } 12 return res; 13 } 14 long long calu(int a){ 15 int len=31; 16 while(len!=-1 && ((a>>len)&1)==0) --len; 17 if(len==-1) return 0; 18 long long res=0; 19 for(int i=1; i<len; ++i){ 20 res+=cnt(0,1,i); 21 } 22 int zero=0,one=1; 23 for(int i=len-1; i>=0; --i){ 24 if((a>>i)&1){ 25 res+=cnt(zero+1,one,i); 26 ++one; 27 }else{ 28 ++zero; 29 } 30 } 31 zero=0; one=0; 32 for(int i=0; i<=len; ++i){ 33 if((a>>i)&1) ++one; 34 else ++zero; 35 } 36 return res+(zero>=one); 37 } 38 int main(){ 39 d[0][0]=1; 40 for(int i=0; i<32; ++i){ 41 for(int j=0; j<32; ++j){ 42 d[i+1][j]=d[i][j]*(i+j+1)/(i+1); 43 d[i][j+1]=d[i][j]*(i+j+1)/(j+1); 44 } 45 } 46 int a,b; 47 while(~scanf("%d%d",&a,&b)){ 48 printf("%lld ",calu(b)-calu(a-1)); 49 } 50 return 0; 51 }