码迷,mamicode.com
首页 > 其他好文 > 详细

LuoguP6218 [USACO06NOV] Round Numbers S

时间:2020-07-24 21:55:37      阅读:76      评论:0      收藏:0      [点我收藏+]

标签:void   赋值   oid   输入格式   遍历   init   开头   解决   ios   

题目描述

如果一个正整数的二进制表示中,\(0\)的数目不小于\(1\)的数目,那么它就被称为「圆数」。

例如,\(9\)的二进制表示为\(1001\),其中有\(2\)\(0\)\(2\)\(1\)。因此,\(9\)是一个「圆数」。

请你计算,区间\([l,r]\)中有多少个「圆数」。

输入格式

一行,两个整数\(l\)\(r\)

输出格式

一行,一个整数,表示区间\([l,r]\)中「圆数」的个数。

样例

输入:\(2 12\)         输出: \(6\)

思路

显然这道题又是一道数位DP。但是这个题的难点和特殊之处就在于它是在二进制下处理的。这就需要我们重新揣度此题的状态。

\(f[i][j][k]\)表示一个有\(i\)位,且其中包括\(j\)\(1\),且从右往左数第\(i\)个数是\(k\)的圆数的个数。这个如何转移呢?

显然的是,若\(j < i\),  \(f[i][j][0]=f[i-1][j][0]+f[i-1][j][1]\),若\(j !=0\), 则\(f[i][j][1]=f[i-1][j-1][0]+f[i-1][j-1][1]\) (这个感性理解一下?)

最后就分成两种情况处理:

1.将位数小于cnt位的圆数累加到答案中

2.和其他数位DP类似,考虑用添数的方法解决问题。若当前遍历到该位为\(1\),那么就存在该位为\(0\)的情况,这时候再枚举\(1\)的位数,考虑还是否能构成圆数,若能构成,累加答案即可。

最后前缀和统计答案即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
typedef long long ll;
int l, r;
ll f[35][35][2];
inline int read(void){
    int f = 1, x = 0;char ch;
    do{ch = getchar();if(ch==‘-‘)f = -1;} while (ch < ‘0‘ || ch > ‘9‘);
    do{ x = x * 10 + ch - ‘0‘;ch = getchar();} while (ch >= ‘0‘ && ch <= ‘9‘);
    return f * x;
}
inline void _init(void){
    f[1][0][0] = 1, f[1][1][1] = 1;//初始化
    for (int i = 2; i <= 32;++i){
        for (int j = 0; j <= i;++j){
            if(j<i) f[i][j][0] = f[i - 1][j][0] + f[i - 1][j][1];//若既有0又有1或只有0
            if(j) f[i][j][1] = f[i - 1][j - 1][0] + f[i - 1][j - 1][1];//若有1或全部是1
        }
    }
    return;
}
inline ll calc(int x){
    int bn[35], cnt = 0;
    ll res = 0;
    if(x==0) return 1;
    while(x){
        bn[++cnt] = x & 1;
        x >>= 1;
    }//二进制拆分
    for (int i = 1; i < cnt;++i){
        for (int j = 0; j <= (i >> 1); ++j)
            res += f[i][j][1];
    }//统计小于cnt位的圆数,(i>>1)这个就是保证了所得的数一定为圆数
    int s0 = 0, s1 = 1;//s1一定要赋值为1,因为无论如何我们讨论的数都是大于1的(为0的情况在开头就舍掉了)
    for (int i = cnt - 1; i >= 1;--i){
        if(bn[i]) for (int j = 0; j <= i;++j){
            if(s0+i-j>=s1+j) res += f[i][j][0];//已确定的0的个数+枚举的0的个数>=已确定的1的个数+枚举的1的个数
        }//                                          s0        +  i  -  j   >=     s1       +     j
        bn[i] ? ++s1 : ++s0;
    }
    return res;
}
int main(){
    l = read(), r = read();
    _init();
    printf("%lld\n", calc(r + 1) - calc(l));
    return 0;
}

LuoguP6218 [USACO06NOV] Round Numbers S

标签:void   赋值   oid   输入格式   遍历   init   开头   解决   ios   

原文地址:https://www.cnblogs.com/ShadowFlowhyc/p/13373831.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!