当前位置:首页 » 《随便一记》 » 正文

1083 Windy数(数位dp)_二哈

6 人参与  2022年02月27日 13:25  分类 : 《随便一记》  评论

点击全文阅读


1. 问题描述:

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

输入格式

共一行,包含两个整数 A 和 B。

输出格式

输出一个整数,表示答案。

数据范围

1 ≤ A ≤ B ≤ 2 × 10 ^ 9

输入样例1:
1 10

输出样例1:
9

输入样例2:
25 50

输出样例2:
20
来源:https://www.acwing.com/problem/content/1085/

2. 思路分析:

分析题目可以知道我们需要求解区间[a,b]满足不包含前导0并且相邻两个数字的差为2性质的数的个数,由这个特点可以知道这道题目属于数位dp的题目。对于数位dp的题目都是类似的套路,借助于树的形式来分析,具体的实现步骤如下:以区间的其中一个端点y为例,首先是需要预处理二维数组f,这样后面在枚举y的每一位的时候左边的分支可以直接计算出来(一般都是组合数),类似于1082 题数字游戏,当前的最高位只与次高位是有关系的,所以我们可以借助于1082题f数组的状态表示和状态计算的思路,定义一个二维数组f,其中f[i][j]表示最高位为j且总共为i位的Windy数的个数,怎么样递推呢?(状态计算),我们可以使用三层循环枚举,第一层循环枚举当前总共有i位,第二层循环枚举i位数字的最高位填j,第三层循环枚举次高位填k,只有当abs(j - k) >= 2说明才是合法的,这样我们通过预处理就将所有总共有i位最高位填k的情况计算出来了,预处理的目的是为了枚举左边分支填0~x-1的时候可以直接计算出对应的结果;预处理f数组之后,我们需要将区间端点y的每一位扣出来存储到nums中,然后逆序枚举nums中的每一位数字,对于当前的第i位数字x = nums[i],我们可以尝试填0~x-1,当第i位填0~x-1的时候就可以直接累加上之前预处理的结果了,进入到循环的下一位的时候表示第i位填x,当我们在枚举的时候发现不满足条件之后可以直接break。这道题目的处理方式与1082题很像的可以对比着理解。

3. 代码如下:

from typing import List


class Solution:
    # 与1082不降数的题目是类似的都是枚举最高位与次高位填的数字是什么
    def init(self, N: int, f: List[List[int]]):
        # 注意这里在枚举的时候需要计算到dp[i][0]的值, 如果不算会出现错误, 因为例如2位数字n = 20可以由dp[1][0]转移过来
        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]
    
    # 计算区间[0, n]的Windy数的个数, 这里将0算不算答案都没有影响, 因为做差之后结果都等于0
    def dp(self, n: int, f: List[List[int]]):
        # 边界, 这里将0归为不是windy数
        if n == 0: return 0
        nums = list()
        while n > 0:
            nums.append(n % 10)
            n //= 10
        # last表示上一个填的数字只要与0~9的数字绝对值差值大于2即可
        res, last = 0, -2
        for i in range(len(nums) - 1, -1, -1):
            x = nums[i]
            # 判断是否是首位, 首位应该从1开始填, 否则可以填0, 枚举当前这一位填小于x的情况
            for j in range(1 if i == len(nums) - 1 else 0, x):
                if abs(j - last) >= 2:
                    res += f[i + 1][j]
            if abs(last - x) >= 2:
                # 当满足条件之后说明可以接着尝试填下一个数字
                last = x
            # 当前的x与上一位填的数字last不满足题目的要求, 说明这一位填x后面不管填什么的方案都是不满足题目要求了直接break
            else:
                break
            # 最右边那个分支
            if i == 0: res += 1
        # 枚举之前没有计算的前导0的特殊情况, 只需要枚举到len(nums) - 1即可, 最高位上面已经累加到答案中了
        for i in range(1, len(nums)):
            # 当前有1~9位最高位为1~9的方案数目
            for j in range(1, 10):
                res += f[i][j]
        return res

    def process(self):
        x, y = map(int, input().split())
        N = 11
        f = [[0] * 10 for i in range(N)]
        self.init(N, f)
        return self.dp(y, f) - self.dp(x - 1, f)


if __name__ == "__main__":
    print(Solution().process())

点击全文阅读


本文链接:http://zhangshiyu.com/post/35479.html

枚举  高位  数字  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1