度的数量(数位dp)

1. 问题描述:

求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:
17=2^4+2^0
18=2^4+2^1
20=2^4+2^2
输入格式
第一行包含两个整数 X 和 Y,接下来两行包含整数 K 和 B。
输出格式
只包含一个整数,表示满足条件的数的个数。
数据范围
1≤X≤Y≤2^31−1,
1≤K≤20,
2≤B≤10
输入样例:
15 20
2
2
输出样例:
3

来源:https://www.acwing.com/activity/content/11/

2. 思路分析:

① 这道题目是关于数位dp知识点的题目,数位dp的题目大部分是求解某一个区间满足某一种性质的数的个数。而且大部分题目都是有基本的套路:令f(t)为区间[0, t]满足题目要求的数目,则区间[x, y]中满足题目要求的数目为:f(y) - f(x - 1),类似于前缀和的思想。首先我们需要计算出当前的数字x中各个位置上的数字存储在数组或者列表中,在循环中通过考虑当前这位的情况,当前这一位主要有两种选择的可能,第一种是当前这一位填最大的数字x1(x1为之前计算的x中当前位上的数值),例如十进制数字76中的第一位为7那么第一位可以填的最大数字是7,第二种情况是当前这一位填0~x1 - 1,这一位后面的低位就可以填任意的数字,因为随便填的时候都不会超过当前最大的数字x,对于x中每一位上的数字都是这样考虑最终就可以计算出[0,x]区间中满足题目要求的个数。

② 对于这道题目来说实际上是将某个区间[0,n]上的数字转换为B进制表示,对于[0,n]区间上的所有数字我们考虑在B进制下对应的位中是否有对应的权重即可,如果B = 3,K = 2,n 有5位,那么我们考虑三进制数字n = a5a4a3a2a1中的位是否有对应的权重即可,例如n = 12001,那么第1,2,5位上就是有对应权重的(当前位置上有权重说明当前位置是可以填1的),结合K=2那么可以将2个1填入到这五个位置中的可能位置,1放置的位置不一样的时候那么组成的数字也是不一样的(这些位置填入的数字要么是0要么是1这样才可以将当前的数字分解成B进制的和),这样通过调整1的位置那么就可以构成区间[0, n]的有K个1对应的数字,所以对于这道题目来说就可以看成是有K个1在这些B进制数字各个位置上的组合方案(本质上是求解组合数)。所以我们首先是需要计算出当前的十进制下的数字r中对应的B进制下的各个位上的数字,在循环中进行递推,从高位到低位进行考虑。举一个比较简单的例子吧:例如十进制下输入数字x = 14那么对应二进制下的数字n = 1110,K = 2,B = 2,遍历当前的数字n中的各位,第一位是1那么当前的位置有权重可以考虑将当前的K = 2个1填入到其他的三位,也即第2~4位中的任意一位,即0110,0101,0011,也即C32 = 3种可能的方案,使用一个变量last来记录已经使用的1的数目,此时last = 1,接着遍历第二位数字1,那是考虑将当前的权重填入其他的位置,此时还有一个1可以填入剩余的第三位或者第四位中的任意一位,为1001,1010,也即C21 = 2,这个时候last = 2,遍历第三位1那么这个时候last已经没有1可以填了,对应1100这种情况,所以总的情况是6种。

③ 我们在写程序只要是当前的位上有权重那么考虑将1填入到剩余的位置,并且当当前位上的数字大于1的时候说明当前的1可以填入到剩余位置中的任意一个位置都不会超过数字x,当我们计算结果之后可以直接break了。

3. 代码如下:

n = 35
f = [[0] * n for i in range(n)]

# 使用递推计算组合数目
def init():
    for i in range(n):
        for j in range(i + 1):
            if j == 0:
                f[i][j] = 1
            else:
                f[i][j] = f[i - 1][j - 1] + f[i - 1][j]


K, B = 0, 0


def dp(n1: int):
    if n1 == 0: return 0
    nums = list()
    # 计算n1的B进制数字的各位
    while n1:
        nums.append(n1 % B)
        n1 //= B
    res = 0
    # 当前使用的1的数目
    last = 0
    # 计算K个1填入到当前的可能的位置
    for i in range(len(nums) - 1, -1, -1):
        x = nums[i]
        if x:
            res += f[i][K - last]
            # 大于1的时候说明当前已经剩余的位置是可以填入1的, 计算组合数然后break即可
            if x > 1:
                if K - last - 1 >= 0:
                    res += f[i][K - last - 1]
                    break
            else:
                # 当前数字为1那么使用1的数目加1
                last += 1
                if last > K: break
        # 最后一个数字可能数1所以需要加上最后一种情况
        if i == 0 and last == K:
            res += 1
    return res


if __name__ == '__main__':
    l, r = map(int, input().split())
    K, B = map(int, input().split())
    init()
    print(dp(r) - dp(l - 1))

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值