AcWing 1081. 度的数量(数位统计dp)

在这里插入图片描述
在这里插入图片描述

题意:

题目就是问:在一个区间[x,y]有多少个符合题意的数

符合题意的数”是指:这个数的B进制表示中,其中有K位是1、其他位全是0

比如样例中B=2,K=2,这个数是18.就是把18化为二进制,18 化为二进制数为:10010,其中有21,其他位都是0.

再比如 B=3,K=2,这个数是17.就是把17化为三进制,17化为三进制为110 ,其中也有21,其余都是0.

思路:

这是一道 数位dp 的题目,数位dp的题目的问法一般如下:

  • 某个区间内满足某种性质的数的个数

对于 数位dp问题 一般有 两个解题方向

  1. ①利用 前缀和 的思想,比如求区间[x, y]中满足条件的数的个数,转化成求[0, y]的个数、[0, x-1]的个数 两者之差
  2. ②利用 树的结构 来考虑(其本质即为:按位分类讨论

该类题目的核心就是根据数位进行分类讨论

  • 第一步,先将数x转化成为B进制,记为N,将转化后的B进制数中的每一位存入一个num数组中。

  • 第二步,在num数组中从高位到低位 依次 进行分类讨论,对于第 i 位数字 x 有三种情况(k含义见“题意”,last 表示i 位之前的所有位取过的 1 的个数

    • x = 0 :此时i 位只能取 0,不影响 1 的个数后面所有位(共 i 位,因为下标是从 0 开始的,下同)可以k-last1,直接讨论后面 i即可。
    • x = 1 :此时可以01,当 i位取 0 时,后面i位都可以随意取值,可取 k - last1 ,当取 1 的时候,后面i位要在小于题目给定数的前提下取值,还要进行讨论,后面 i 位不能随便取,也就不是组合数(且只能取k - last - 11)。
    • x > 1 :则 i 位上 可以取 1 ,0 ,并且后面 i可以随意取k-last-1个1。除了0 、1的其他值不能取,因为其他值对于本题无意义(换句话说就是不符合题意)。

我们可以构造一棵递归搜索树,我们要求的答案 res 即为下图所有圆圈部分之和
在这里插入图片描述

另外,组合数递推公式Ckn−1+Ck−1n−1=CknCn−1k+Cn−1k−1=Cnk
,我们将组合数存在数组 c 中,c[i][j]表示 从 i 个数中选出 j 个数的组合数。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 35;

int K, B;
int c[N][N];

void init()//求组合数
{
    for(int i=0; i<=N; ++i)
    {
        for(int j=0; j<=i; ++j)
        {
            if(!j) c[i][j] = 1;
            else c[i][j] = c[i-1][j-1] + c[i-1][j];
        }
    }
}


int dp(int n)
{ 
    if (!n) return 0;//如果 n==0,那么就直接返回 0(特判)
    vector<int> num;
    while (n) num.push_back(n%B), n /= B;
    int res = 0;
    int last = 0;//表示已经取了多少个 1

    for(int i=num.size()-1; i>=0; --i)//从最高位到最低位对每一位数讨论
    {
        int x = num[i];
        if(x) //求左分支中数的个数
        {
            res += c[i][K-last];//加上第 i 位取 0 的时候的组合数,也就是对于后面 i 位取 k-last 个 1 的数量
            if(x > 1)//如果 x>1,就可以直接用组合数表示出来,不用进行讨论,也就是 i 位取 1 的时候,后面 i 位随便取 k-last-1 个 1
            {
                if(K - last - 1 >= 0) res += c[i][K-last-1];
                break;
            }
            else//如果 x==1,那么 i 位取 1 的时候,还要进行讨论,后面 i 位不能随便取,也就不是组合数
            {
                ++last;
                if(last > K) break;
            }
        }
        if (!i && last==K) ++res;// 最右侧分支上的方案,即对于最后一位进行的特殊考虑
    }
    return res;
}

int main()
{
    init();

    int l, r;
    cin>>l>>r>>K>>B;

    cout<<dp(r) - dp(l-1)<<'\n';

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值