poj3252 Round Numbers

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;
}

 

posted @ 2018-10-13 22:22  kafuuchino  阅读(183)  评论(0编辑  收藏  举报