题401.数位dp-acwing-1081. 度的数量


题401.数位dp-acwing-1081. 度的数量


一、题目

在这里插入图片描述

二、题解

欲求区间[X,Y]中满足性质的数的个数,我们可以想着去求小于m的数中满足性质的个数f(m),然后利用前缀和思想,f(X,Y)=f(Y)-f(X-1)得到最后结果。
现在考虑如何求f(m):
对于上界m,设按B进制表示为an-1an-2an-3an-4…a1a0,则我们可以据此来给每一位填数,填0或者1(因为是要求<=m的数中恰好等于K个互不相等的B的整数次幂之和的数的个数,显然每个加数的系数为1),则以填最高位为例,新数该位要么填0-an-1-1(当然本题只能最大到1),要么填an-1(当然也是最大只能填1),对于前者之后的位不论怎么填都可以满足比n来得小,则总的方案数就是加上C(n,K-last)+C(n,K-last-1),其中last为之前填过的1的个数,对于后者则last++,然后须按照上述方式接着讨论,则整个过程可以按树讨论如下:
在这里插入图片描述
则我们还需要开局预处理一个组合数,对此可利用C(i,j)=C(i-1,j)+C(i-1,j-1)递推求取。
代码如下:

#include <bits/stdc++.h>

using namespace std;

const int maxn=35;//B进制最小为2,则由题目给的X、Y数据范围maxn设为>32

int K,B,l,r;
int f[maxn][maxn];//f[i][j]表示i个数里头选j个数出来的方案数,即组合数C(i,j)

//求组合数
void init()
{
    for(int i=0;i<maxn;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(!j) f[i][j]=1;//i个数里头选0个数的方案数为1
            else f[i][j]=f[i-1][j]+f[i-1][j-1];//C(i,j)=C(i-1,j-1)+C(i-1,j)
        }
    }
}

int dp(int n)//求<=n的满足性质的数的个数
{
    if(!n) return 0;//

    //分离出在B进制下n的各个位
    vector<int> nums;
    while(n) nums.push_back(n%B),n/=B;

    int res=0;//方案数
    int last=0;//存储之前用过的1的个数
    for(int i=nums.size()-1;i>=0;i--)//从高位向低位枚举B进制下n的各个位
    {
        int x=nums[i];
        if(x>0)//x>0时才有左分支(填0到x-1范围内的数,但注意本题只能填0到1)
        {
            res+=f[i][K-last];//左分支,当前位填0,则方案数加上从i位中挑K-last个位来填1的方案数
            if(x>1)//左分支,当前位要填1,必须x>1
            {
                if(K-last-1>=0) res+=f[i][K-last-1];//当前位填1,则方案数加上从i位中挑K-last-1个位来填1的方案数
                break;//右分支,当前位填x,则必定不满足数性质(它是只能填0、1),直接break
            }
            else//x==1则左分支,当前位只能填0
            {
                last++;//考虑右分支,当前位填x,即1,则之前用过的1的个数++
                if(last>K) break;//之前用过的1的个数超过了能够填1的个数直接break
            }
        }
        if(!i&&last==K) res++;//遍历到最低位,且最低一位只能为0(上面操作考虑过了该位填1的情况),此时res++
    }
    return res;
}

int main()
{
    init();//预处理组合数
    cin>>l>>r>>K>>B;
    cout<<dp(r)-dp(l-1);//f(X,Y)=f(Y)-f(X-1)
}

三、关于数位dp

技巧1:[X,Y] => f(Y)-f(X-1)
技巧2:树


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值