Round Numbers poj3252
题目大意:求一段区间内Round Numbers的个数。
注释:如果一个数的二进制表示中0的个数不少于1的个数,我们就说这个数是Round Number.给定区间l,r<=$2cdot 10^9$。
想法:又是一道数位dp裸题。我们先来设状态:dp[i]表示二进制表示下有i为而且第一位是1的Round Number的个数。
这题的特殊之处在于我们并不需要转移?因为我们可以直接求出任意的dp[i]。显然,我们的i位数的第一位是1,所以,后面0的个数一定不能少于(i-1)/2+1.我们在后面的i-1位当中去组合数即可。
然后,我们思考数位dp的边界:对于一个数来讲,我们可以将所有的位数小于tot的dp全部加起来(tot是该数的二进制表示下的位数)。然后,我们显然只需要对于这个数的二进制从左到右枚举:弄两个计数器分别记录之前经过了多少个0.对于当前数码,如果是0,略过即可。如果是1,则在它之后所有满足该位是0前面满足的数的Round Number都是满足条件的Round Number。,统计即可。
最后,附上丑陋的代码... ...
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int C[33][33]; void before_hand()//预处理组合数 { C[0][0]=1; C[1][0]=1;C[1][1]=1; for(int i=2;i<33;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; } } int bits[33]; int dispose(int n) { if(n<=1) return 0; int tot=0; while(n>0)//二进制拆分 { if(n&1) bits[tot++]=1; else bits[tot++]=0; n>>=1; } int ans=0; for(int i=tot-1;i>0;i--)//将位数小于tot的全部加一起 { if(i%2==0) ans+=((1<<(i-1)))/2; else ans+=((1<<(i-1))-C[i-1][(i-1)/2])/2; } int count_for_0=0,count_for_1=0;//两个计数器(字面意思) for(int i=0;i<tot;i++) { if(bits[i]==0) count_for_0++; else count_for_1++; } if(count_for_0>=count_for_1) ans++; count_for_0=0; count_for_1=1; for(int i=tot-2;i>=0;i--) { if(bits[i]==1) { for(int j=i;j>=0&&j+count_for_0+1>=i-j+count_for_1;j--) ans+=C[i][j];//简单点儿来说就是直接统计后面的dp值 count_for_1++; } else count_for_0++;//如果是0,就直接增加计数器 } return ans; } int main() { before_hand(); int a,b; while(scanf("%d%d",&a,&b)!=EOF) { printf("%d ",dispose(b)-dispose(a-1));//防止重叠 } return 0; }
小结:数位dp的恶心之处就是边界特判,沉下心来写一写,不行就把代码拿去copy算了,毕竟这道题的本质不是代码... ...