poj3252 Round Numbers
组合数学(数位dp)
(没想到书上的标程还要看脸TAT)
(我以后再也不在poj上用scanf/printf了TAT)
(poj的题都默认多组数据的吗TAT)
tips:信息学奥赛数学一本通的标程和部分Baidu上的代码访问数组越界,脸黑的会GG(比如我QAQ),详细见下
题意:求闭区间[a,b]内有多少个数转换成二进制后0的个数比1多,1<=a<b<=2*(10^9)
显然是数位dp的套路: solve(b+1)-solve(a) (本题的solve(n)不包括n)
于是我们解决的范围就变成 [0,n] 了
先把n转成二进制(灰常显然),扔到数组wt里
蓝后我们分类讨论
1.所求数的长度<n的长度
我们可以用组合数直接算出。(可以预处理)
枚举所求数的长度 i=1 to len-1-1 (第一位一定是1所以多扣一位)
再枚举所求数中0的个数 j= i/2+1 to i ,累加C( i , j )就完事了
2.所求数的长度==n的长度
我们把wt数组拿来从高位到低位枚举 i= len-1 to 1 (∵↑↑ ∴len-1)
当 wt[i]==0时,我们用变量 z 统计 0的个数
当 wt[i]==1时:
我们先假定该位为0,那么接下来的若干位无论怎么排都严格小于n
∴也可以枚举0的个数用组合数瞎搞
j= max(0,(len+1)/2-(z+1)) to i-1
attention:取max防止数组越(负)界(标程的锅就在这)
最后累加答案,end.
use 2h30min,6 submissions TAT
#include<iostream> using namespace std; int max(int a,int b) {return a>b ?a:b;} int wt[35],a,b,c[35][35]; int solve(int n){ int res=0,z=0,len=0; for(;n;n>>=1) wt[++len]=(n&1); //转二进制 for(int i=1;i<len-1;++i) //长度<n for(int j=i/2+1;j<=i;++j) res+=c[i][j]; for(int i=len-1;i>=1;--i) //长度==n if(wt[i]){ for(int j=max(0,(len+1)/2-z-1);j<i;++j) res+=c[i-1][j]; }else ++z; return res; } int main(){ for(int i=0;i<=32;++i)//组合数预处理 for(int j=0;j<=i;++j) c[i][j]=(!j||i==j)?1:c[i-1][j-1]+c[i-1][j]; while(cin>>a>>b) cout<<solve(b+1)-solve(a)<<endl; return 0; }