POJ 3252(数位DP || 组合数)

来源:互联网 发布:知乎搞笑问答 编辑:程序博客网 时间:2024/06/11 02:20

Description

The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro, Sham, Bo', and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can't even flip a coin because it's so hard to toss using hooves.

They have thus resorted to "round number" matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both "round numbers", the first cow wins,
otherwise the second cow wins.

A positive integer N is said to be a "round number" if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.

Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many "round numbers" are in a given range.

Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).


Input

Line 1: Two space-separated integers, respectively Start and Finish.

Output

Line 1: A single integer that is the count of round numbers in the inclusive range Start..Finish

Sample Input

2 12


Sample Output

6




题目大意

给定一个闭区间,求区间内有多少个数满足转为二进制表示后0的个数大于等于1的个数

1 ≤ Start < Finish ≤ 2*e9




思路1 数位DP

dp[k][i][j]   k为长度,i为0的个数,j为1的个数

考虑添加“first”变量,判断当前是不是首位,如果传进来的first=1并且下一位枚举i=0,则传下去的first依然是1

如果不是首位的话,则直接根据枚举的i来确定num0+1还是num1+1即可



代码示例

//#define LOCAL#include<iostream>#include<algorithm>#include<cstdio>#include<stdlib.h>#include<string.h>#include<algorithm>using namespace std;int m,n;int dp[50][50][50];int bit[50];//dp[k][i][j] k为长度,i为0的个数,j为1的个数int dfs(int pos,int num0,int num1,int first,int limit){    //num0,num1分别为0,1的个数,first表示第一个1的位置    if(pos<0) return num0>=num1;//套路,num0>=num1符合题意    if(!limit&&dp[pos][num0][num1]!=-1) return dp[pos][num0][num1];    int ans=0;    int up=limit?bit[pos]:1;//注意只有0,1    for(int i=0;i<=up;++i){        int t=first&&(i==0);//当first为1并且i=0时,表示第一位1还未放,下一位依然是“首位”num0=num1=0        //当t为0,则根据枚举(i==0)来确定放0还是10        ans+=dfs(pos-1,t?0:num0+(i==0),t?0:num1+(i==1),t,limit&&i==up);    }    if(!limit) dp[pos][num0][num1]=ans;    return ans;}int solve(int n)//存储二进制位{    int len=0;    while(n)    {        bit[len++]=n%2;        n>>=1;    }    return dfs(len-1,0,0,1,1);}int main(){    //std::ios::sync_with_stdio(false);    #ifdef LOCAL        freopen("read.txt","r",stdin);    #endif    memset(dp,-1,sizeof(dp));    while(cin>>m>>n)    {        printf("%d\n",solve(n)-solve(m-1));    }    return 0;}



思路2 组合数

考虑求解1~数m中有多少符合题意解,最后output  : solve(m)-solve(n-1)

分两类

1.二进制位数较小

一定满足位数小的数值小,所以都能取到。直接用组合数计算

例如33=100001 二进制6位 考虑1~5位的情况 这时候每一位首一定是1

5位:第一位一定为1  后面4位 C(4,3)+C(4,4)

4位:第一位一定为1  后面3位 C(3,2)+C(3,3)

......

......

......(可类比)


公式推导:

观察可知后面的选择与长度的奇偶性有关

当len-1(这个-1是因为确定了第一位为1)为偶数:

C(len-1,  (len-1)/2+1  )  +  C(len-1,  (len-1)/2+2  )  +......+  C(len-1,len-1) =( 2^(len-1)  -  C(len-1,(len-1)/2) )/2

证明:即证C(a,a/2+1)+C(a,a/2+2)+......+C(a,a)=(  2^a-C(a,a/2)  )   /2

因为C(a,0)+C(a,1)+C(a,2)+......+C(a,a/2)+......+C(a,a-1)+C(a,a)=2^a

且前后蓝色两部分相等  移项得证


当len-1(这个-1是因为确定了第一位为1)为奇数:

C(len-1,len/2)+C(len-1,len/2+1)+......+C(len-1,len-1)=( 2^(len-1) )/2    

证明同理



2.二进制位数相同

位数相同时,采用的是从高位向低位处理,如果该位是0,continue

如果该位是1,将其视为0,并计算后面低位满足题意的数量(这时候高位1变0了,一定在范围内)

具体可以参考代码注释


代码示例

#include<iostream>#include<algorithm>#include<cstdio>#include<stdlib.h>#include<string.h>#include<algorithm>using namespace std;int c[35][35];void init()//组合数{    c[0][0]=1;    c[1][0]=c[1][1]=1;    for(int i=2;i<35;++i){        c[i][0]=1;        for(int j=1;j<i;++j)            c[i][j]=c[i-1][j-1]+c[i-1][j];        c[i][i]=1;    }    //cout<<c[5][2]<<" test "<<c[10][3]<<endl;}int bit[35];//数位int solve(int n){    if(n<=1) return 0;    int len=0;    while(n>0)//注意高位在数组后部    {        if(n&1) bit[len++]=1;        else bit[len++]=0;        //cout<<bit[len-1]<<' ';        n>>=1;    }    //cout<<endl;    int ans=0;    for(int i=len-1;i>0;i--){//计算位数小的        if(i%2==0) ans+=((1<<(i-1))/2);        else ans+=((1<<(i-1))-c[i-1][(i-1)/2])/2;    }    //cout<<ans<<" test ans"<<endl;    int cnt0=0,cnt1=0;    for(int i=0;i<len;++i){//这个数特判一下        if(bit[i]==0) cnt0++;        else cnt1++;    }    if(cnt0>=cnt1) ans++;    cnt0=0;    cnt1=1;    for(int i=len-2;i>=0;--i){        if(bit[i]==1){//按照0考虑 因为按照1考虑时,后面可能会有大的数                //另外按照0考虑的数量包含按照1考虑的数量,因为是1都满足0>=1,是0也一定满足            for(int j=i;j>=0&&j+cnt0+1>=i-j+cnt1;--j)//j表示有多少位为0                ans+=c[i][j];            cnt1++;        }        else cnt0++;    }    return ans;}int main(){    int m,n;    init();    while(cin>>n>>m)    {        cout<<solve(m)-solve(n-1)<<endl;    }    return 0;}



由本题可以看出,数位DP像是组合数的升级版,两者都有递推的思想(组合数init时采用的递推式,数位DP记忆化搜索)

但数位DP可以解决那些看起来不这么规律的问题,限制条件比较多样

而本题组合数是可以直接计算的