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,需要注意的是这道题目不包含前导0因为包含前导0之后可能会导致到不满足Windy数的要求,而在递推的时候是考虑了前导0的所有我们在枚举高位的时候一定要从1开始枚举,最后再考虑最高位为0的情况(隐含的0--例如首位填0那么下一位可以随便填而不用计算相邻两位的绝对值之差是否小于等于2)。这道题目的处理方式与1082题很像可以对比理解其中的解决过程。

3. 代码如下:

迭代写法:

from typing import Listclass 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] = 1for i in range(2, N):# 这里考虑了最高位为0的情况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 0nums = list()while n > 0:nums.append(n % 10)n //= 10# last表示上一个填的数字只要与0~9的数字绝对值差值大于等于2即可res, last = 0, -2for 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后面不管填什么的方案都是不满足题目要求了直接breakelse: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 resdef process(self):x, y = map(int, input().split())N = 11f = [[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())

dfs:

下面补充一下数位dp的dfs写法,使用dfs写法相对于迭代式的数位dp写法的思维量和代码量都比较小,并且计算答案的速度与迭代式的数位dp都是差不多的,一般来说数位dp的dfs需要结合记忆化搜索实现(声明相应维度的数组来记录求解过的结果),如果不使用记忆化搜索来解决那么很容易就超时,其实使用dfs和迭代式的数位dp一样都是有固定的套路的,思考过程也是类似的,首先根据题目需要满足的性质确定dfs方法中需要传递哪些动态变化的参数,对于这道题目来说,需要传递的参数:当前还剩下k位要填的数字,可以理解为当前递归的位置,上一位填的数字是last(要求相邻两个数字之差的绝对值大于等于2所以需要记录上一个填的数字last),上一位填的数字是否达到了最大值lim,也即填的是x = nums[k - 1],使用这个标记是为了判断当前这一位可以填哪些数字,上一位是否是首位填0的标记isStart,记忆化搜索的本质其实就是根据这些动态变化的参数设置数组的维度来记录已经求解过的值,其实也很好理解如果我们之前求解过方法中一模一样的参数值,接下来的递归求解过程不就是重复求解了吗,也即与之前一模一样求解的过程,所以我们需要使用一个数组来记录递归过程中已经求解过的值;考虑到在递归的时候达到上限lim的次数是比较少的,所以只需要记录没有达到上限lim的方案数目到数组中即可,所以根据上面的考虑我们可以声明三维数组(其实省略掉了一维记录使用一个if判断来表示不是上限的lim的情况),其中f[i][j][k]表示还剩下i位要填的数字,上一位填的数字为j,首位是否填0的情况k,下面是使用三维数组f记忆化搜索的写法:

from typing import Listclass Solution:# lim表示是否达到上界也即上一位填的是否是最大值x = nums[k - 1], isStart表示当前是否是首位填数0的情况, 用来处理前导0的情况, 使用这个标记来特判掉首位填0的情况因为上一位是0那么下一位的数字其实是可以填任何数字的而不特判的情况下会筛选掉与0绝对值小于2的数字的情况所以答案会变少所以这里使用isStart来判断是否是首位填0的情况, last用来记录上一个数字def dfs(self, k: int, last: int, lim: bool, isStart: int, nums: List[int], f: List[List[List[int]]]):if k == 0: return 1# 没有达到上限的情况下数组值大于等于0说明之前已经求解过了直接返回即可if not lim and f[k][last][1 if isStart else 0] >= 0: return f[k][last][1 if isStart else 0]res = 0# 判断当前这一位可以填的最大数字up = nums[k - 1] if lim == 1 else 9for i in range(up + 1):# 上一位与当前这一位填的绝对值之差需要大于等于2或者是上一位是首位并且填0的情况那么这一位可以填iif abs(i - last) >= 2 or isStart:res += self.dfs(k - 1, i, lim and i == up, isStart and i == 0, nums, f)# 记忆化的过程, 只记录没有到达上限的情况, 判断的时候也是判断没有达到上限的情况, 记录与判断是是一一对应的if not lim: f[k][last][1 if isStart else 0] = resreturn res# 数位dp的dfs写法其实与迭代式的写法的思考过程都是类似的, 都是考虑每一位填什么的情况, 每一次累加的是下一位到最后一位求解的方案数目那么最终得到的就是考虑每一位填什么的方案数目def dp(self, n: int):nums = list()# 将n的每一位抠出来while n > 0:nums.append(n % 10)n //= 10# 初始化为-1之后才可以判断是否求解过当前的数组值f = [[[-1] * 2 for i in range(10)] for j in range(11)]return self.dfs(len(nums), -2, True, True, nums, f)def process(self):a, b = map(int, input().split())return self.dp(b) - self.dp(a - 1)if __name__ == "__main__":print(Solution().process())

补充一下四维数组的写法,这样就不用使用上面的if判断是否到达上限了,因为第四维就可以表示是否达到上限:

from typing import Listclass Solution:# 下面这个写法是根据根据dfs方法中动态变化的参数声明与之一一对应的思维数组f来记录的记忆化搜索方法def dfs(self, k: int, last: int, lim: bool, isStart: int, nums: List[int], f: List[List[List[int]]]):if k == 0: return 1if f[k][last][1 if isStart else 0][1 if lim else 0] >= 0: return f[k][last][1 if isStart else 0][1 if lim else 0]res = 0up = nums[k - 1] if lim == 1 else 9for i in range(up + 1):if abs(i - last) >= 2 or isStart:res += self.dfs(k - 1, i, lim and i == up, isStart and i == 0, nums, f)f[k][last][1 if isStart else 0][1 if lim == 1 else 0] = resreturn resdef dp(self, n: int):nums = list()while n > 0:nums.append(n % 10)n //= 10f = [[[[-1] * 2 for i in range(2)] for j in range(10)] for k in range(11)]return self.dfs(len(nums), -2, True, True, nums, f)def process(self):a, b = map(int, input().split())return self.dp(b) - self.dp(a - 1)if __name__ == "__main__":print(Solution().process())

1083 Windy数(数位dp)相关推荐

  1. 洛谷 P2657 [SCOI2009] windy数 数位DP

    大家觉得写还可以,可以点赞.收藏.关注一下吧! 也可以到我的个人博客参观一下,估计近几年都会一直更新!和我做个朋友吧!https://motongxue.cn 文章目录 P2657 [SCOI2009 ...

  2. bzoj 1026: [SCOI2009]windy数 数位DP算法笔记

    数位DP入门题之一 也是我所做的第一道数位DP题目 (其实很久以前就遇到过 感觉实现太难没写) 数位DP题目貌似多半是问从L到R内有多少个数满足某些限制条件 只要出题人不刻意去卡多一个$log$什么的 ...

  3. 【bzoj1026】[SCOI2009]windy数 数位dp

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

  4. BZOJ1026 [SCOI2009]windy数 数位dp

    欢迎访问~原文出处--博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1026 题目概括 求区间[A,B]中有多少数满足下面的条件. 条件:该数相邻两位之差不小于2. 题解 ...

  5. AcWing1083. Windy数(数位DP)题解

    题目传送门 题目描述 Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数. Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共 ...

  6. WINDY数----数位dp

    题目链接:https://ac.nowcoder.com/acm/problem/20268 题目描述 windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数 ...

  7. P2657 [SCOI2009]windy数(数位dp)

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

  8. 洛谷P2657 windy 数 数位dp

    题目背景 windy 定义了一种 windy 数. 题目描述 不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数.windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共 ...

  9. BZOJ1026: [SCOI2009]windy数(数位dp)

    题意 题目链接 Sol 很zz的数位dp $f[i][j]$表示第$i$位,前一位是$j$的方案数 转移的时候枚举一下是否相同即可 注意当lim达到上界的时候是不能记忆化的! /**/ #includ ...

  10. 题解 BZOJ1026 luogu P2657 [SCOI2009]windy数 数位DP

    BZOJ & luogu 看到某大佬AC,本蒟蒻也决定学习一下玄学的数位$dp$ (以上是今年3月写的话(叫我鸽神$qwq$)) 思路:数位$DP$ 提交:2次 题解:(见代码) #inclu ...

最新文章

  1. python中的变量、Debug和数据类型
  2. python可以做什么有趣的东西-python能做哪些生活有趣的事情
  3. 201521123031 《Java程序设计》第6周学习总结
  4. MySQL获取每个分类下面的前三条数据
  5. Guice 1.0 用户指南
  6. mysql in优化_MySQL 探秘: 1 整体架构
  7. 编程语言python怎么读-Python入门学习的计算机程序设计语言是怎样的?
  8. 论文、报告中那些乱七八糟的图(甘特图、卡吉图,桑基图,小提琴图,弦图,螺旋图,风玫瑰图)
  9. TIKTOK:什么是CPA/CPS?
  10. linux中ifconfig命令作用,ifconfig命令作用范围的是什么
  11. php开发微信会员系统,PHP实例:微信公众号实现会员卡领取功能
  12. 通过naa在esxi主机上找到物理磁盘的位置
  13. 计算机内存条如何区分频率,怎么看内存条频率,详细教您怎么看内存条频率
  14. 爬取北京二手房数据信息(python)
  15. 计算机专业买笔记本有什么要求,笔记本电脑什么配置好?硬核选购指南来了!...
  16. 使用Android Studio编写一个简单的音乐盒
  17. Spark Sql 聚合
  18. 论文阅读《A Multi-State Constraint Kalman Filter for Vision-aided Inertial Navigation》1
  19. GetKeyState、GetAsyncKeyState、GetKeyboardState函数的区别:
  20. 【数据库】Navicat编写MySQL自定义函数详解

热门文章

  1. Android-蓝牙的网络共享与连接分析
  2. [ZT]组策略软件分发部署
  3. rem 用户改变字体大小_用户可以更改字体大小
  4. Java实现短链接URL生成
  5. 第02章 HTML基本标签
  6. (一)大彩屏幕 进行串口通信
  7. Pygame实战:我妈50岁,戒不掉消消乐!看我给您安排一款百变款消消乐!厉害!
  8. orz正在流行的符号---失意体前躯
  9. kubevirt 存储 网络 监控
  10. 18V降压3.3V,15V降压3.3V的降压IC和LDO芯片方案