Round Numbers(poj 3252)

题意:算出区间内二进制中0的个数大于等于1的个数的数字有多少个

/*
  本来以为用数位DP搞,但是组合数更简单。
  我们设n的二进制长度为len。 
  ①:先考虑长度小于len的数字。
      这里以数字22为例,二进制拆成10110,len=5。
      len=1时,只能是1(题目要求是正数);
      len=2时,第一位是1,剩下的1位,至少有1个0,ans+=C(1,1);
      …… 
        len=k时,第一位是1,剩下的len-k位,如果至少要有p个0,那么ans+=C(len-k,p)+...+C(len-k,len-k)。
  ②:再考虑长度等于len的数字。
      第一位是1。  
      第二位是0,所以第二位不能为1,必须是0。 
      第三位为0的话,后面两位可以有1个0,2个0,ans+=C(2,1)+C(2,2)。 
      接下来把第三位恢复为1,看第四位。假如第四位是0,后面一位必须是0,ans+=C(1,1)。  
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 51
#define lon long long
using namespace std;
int c[N][N];
void getc(){
    for(int i=1;i<N;i++)
        for(int j=0;j<=i;j++){
            if(j==0||i==j) c[i][j]=1;
            else c[i][j]=c[i-1][j]+c[i-1][j-1];
        }        
}
lon solve(lon n){
    if(n==1) return 0;
    lon ans=0;
    int a[N]={0},len=0;
    while(n){
        a[++len]=n%2;
        n>>=1;
    }
    reverse(a+1,a+len+1);
    for(int i=1;i<len;i++)
        for(int j=(i-1)/2+1;j<i;j++)
            ans+=(lon)c[i-1][j];
    int p0=0,p1=1;
    for(int i=2;i<len;i++){
        if(!a[i]) {p0++;continue;}
        for(int j=len-i;2*j+p0+1>=p1+len-i;j--)
            ans+=(lon)c[len-i][j];
        p1++;
    }
    if(a[len]&&p0+1>=p1) ans++;
    return ans;
}
int main(){
    getc();
    lon a,b;
    cin>>a>>b;
    cout<<solve(b+1)-solve(a);
    return 0;
}
 
原文地址:https://www.cnblogs.com/harden/p/6288028.html