windy数(数位dp)

1. 问题描述:

windy 定义了一种 windy 数。不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。 windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 windy 数?

输入格式

包含两个整数,A B。

输出格式

一个整数

输入输出样例

输入

1 10

输出

9

输入

25 50

输出

20

数据范围

100%的数据,满足 1 <= A <= B <= 2000000000 

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

2. 思路分析:

① 这道题目是一道经典的数位 dp 知识点的入门题目,与之前 acwing 中数字游戏的题目是类似的,只是题目中数字的位的限制条件不一样而已,但是整体的思路都是一样的。首先需要声明一个二维列表 f 用来已处理,其中 f[i][j] 的含义为当前有 i 位并且最高位为 j 的数目,我们可以使用一个init 方法递推得到 f 列表的值,使用 init 方法预处理的目的是为了能够直接计算出树中左边方案数目(将数字 x 的所有位看成是一棵树,分为左分支与右分支,左边分支考虑当前这一位填入 0~x-1,右边的分支填入 x 对于每一位都是这样处理,所以可以将其看成是一棵树),init 方法中得到列表 f 的值其实也是一个动态规划递推的过程,由小的位数递推到大的位数。然后使用一个 dp 方法计算出 [0,x] 区间中符合条件的数目,那么区间 [x,y] 中符合题目要求的数目为 f(y) - f(x - 1)。dp 方法中首先是将数组 x 的各位存储到一个列表 nums 中,从高位到低位开始遍历 nums 列表中的每一位,因为 windy 数是不包含前导 0 的(包含前导 0 会对后面的低位的数字造成影响),所以对于最高位我们是不可以填入 0 的,所以在遍历当前 nums 中的位的时候判断当前位是否是最高位,如果是最高位那么当前位置尝试的范围为 1~nums[i] - 1,否则为 0~nums[i] - 1,在循环中可以使用 i == len(nums) - 1 判断即可,如果成立那么从 1 开始否则从 0 开始,对于当前这一位尝试 0~nums[i - 1] 的数字,因为之前预处理过所以可以直接将 f 列表对应位置的值累加到结果中,并且声明一个 last 变量来记录前缀信息,对于这道题目 last=nums[i]。最后判断是否可以到达最右边的那个分支,如果可以那么 res++,其实举实际的例子会更容易理解一点。

② 因为 ① 中考虑的是不存在前导 0 的情况,最高位是从 1 开始的,实际上 windy 数有前导0的情况下是会影响最终的结果的,例如两位的 windy 数,如果存在前导 0 那么只有 02,03,04...09才是满足的,忽略掉了 01 这个数字,三位的 windy 数也会筛选掉一些结果,比如第二位只能是 2 之后的数字,所以把像 013...这样的 windy 数也筛选掉了,所以我们需要额外的情况。我们其实是就可以将最高位看成是0但是这个 0 被隐藏掉了所以总共的位数是少一位的,所以我们需要额外计算出总共有 1~len(nums) - 1位的数字,并且最高位为 1~9 的 windy 数,这样就可以忽略掉前导 0 的影响,这也是在遍历 nums 列表中的时候当前位为最高位的时候不能够直接考虑填 0 的原因,所以我们在最后可以枚举一下从 1~len(nums) - 1 范围的 f[i][j] 将其累加到结果中即可。在数位 dp 解决的过程中计算 [0,x] 中的数目都会计算前导 0 的情况,也即 01,02....001,002.....,这些前导 0 一般是没有什么影响的,但是对于这道题目来说是有影响的。

N = 11
f = [[0] * 10 for i in range(N)]


# 初始化f列表, f[i][j]表示当前有i位数字且最高位为j的数目
def init():
    for i in range(10):
        f[1][i] = 1
    for i in range(2, N):
        for j in range(10):
            for k in range(10):
                if abs(j - k) >= 2:
                    f[i][j] += f[i - 1][k]


def dp(n: int):
    if n == 0: return 0
    nums = list()
    while n:
        nums.append(n % 10)
        n //= 10
    res = 0
    # 只要是比1~9的数字的绝对值大于等于2即可
    last = -2
    for i in range(len(nums) - 1, -1, -1):
        x = nums[i]
        # 如果当前是最高位那么应该从1开始否则从0开始
        for j in range(i == len(nums) - 1, x):
            if abs(j - last) >= 2:
                res += f[i + 1][j]
        if abs(x - last) >= 2:
            last = x
        else:
            break
        # 最右边的那个分支
        if i == 0: res += 1
    # 可以看成是前导0隐藏掉的情况例如02 04 06...这些数字可以隐藏掉前导0那么也属于正确答案
    for i in range(1, len(nums)):
        for j in range(1, 10):
            res += f[i][j]
    return res


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

你可能感兴趣的:(动态规划,数位dp,acwing-提高,动态规划,算法)