Round Numbers

Round Numbers

寻找区间\([l,r]\)中的round数的个数,定义round数为二进制位下0比1多的数的个数,\(1 ≤ l < r ≤ 2,000,000,000\)

明显的数位递推题,接下来所有的套路都是在二进制下的讨论,只要会求\(0\sim n\)的符合条件的数,两式相减,即答案。

因为既要表现位数又要表现0和1的多少关系,不妨设\(dp[i][j]\)为i位数的0的个数减去1的个数为j的方案数(含前导0),同时设\(db[i][j]\)为i位数以内的0的个数减去1的个数为j的方案数(不含前导0),有
(转移策略过多,此处采取顺转移的方式)

\[dp[i+1][j+1]+=dp[i][j],dp[i+1][j-1]+=dp[i][j]\]

\[db[i+1][j]+=db[i][j],db[i][j-1]+=dp[i][j]\]

边界:\(dp[0][0]=db[i][j]=1\)

接下来照着数位递推的套路,刚开始处理首位的时候,一个数都没填调用db数组,即\(ans+=\sum_{j\geq 0}dp[n][j]\)(设最高位为n),当前面的数已经确定了,我们要保存前面的数0的个数-1的个数记做k,那么ans即累加\(\sum_{j\geq -k-1}dp[i][j]\)(假设当前处理第i位),注意一个细节,就是这样数位递推对于边界这个数是考虑不到的,要特判,不妨边界++。

参考代码:

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define ll long long
using namespace std;
int num[32];
const int zero(31);
ll dp[32][zero*2+1],db[32][zero*2+1];
il ll ask(int);
il void prepare();
int main(){
    int a,b;prepare();
    scanf("%d%d",&a,&b);
    printf("%lld",ask(b)-ask(a-1));
    return 0;
}
il ll ask(int n){
    ll ans(0);++n,num[0]=0;
    do num[++num[0]]=n&1,n>>=1;while(n);
    if(num[0]==1)return 1;
    for(int i(num[0]),j,k,l(0);i;--i){
        for(j=0;j<num[i];++j){
            if(i==num[0])continue;
            for(k=zero-l-1;k<63;++k)
                ans+=dp[i-1][k];
        }if(j)--l;else ++l;
    }
    for(int i(zero);i<63;++i)
        ans+=db[num[0]-1][i];
    return ans;
}
il void prepare(){
    dp[0][zero]=db[0][zero]=1;
    for(int i(0),j;i<31;++i)
        for(j=0;j<63;++j){
            if(dp[i][j]){
                dp[i+1][j+1]+=dp[i][j];
                dp[i+1][j-1]+=dp[i][j];
                db[i+1][j-1]+=dp[i][j];
            }if(db[i][j])db[i+1][j]+=db[i][j];
        }
}

转载于:https://www.cnblogs.com/a1b3c7d9/p/11208937.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值