POJ3252 Round Numbers(不重复全排列)

题目问区间有多少个数字的二进制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\n",calu(b)-calu(a-1));
49     }
50     return 0;
51 } 

 

posted @ 2016-02-01 11:38  WABoss  阅读(205)  评论(0编辑  收藏  举报