• 数位DP - 带49的数


    目录

    题目来源

    题目描述

    输入描述

    输出描述

    用例

    题目解析

    算法源码


    题目来源

    20200817-数位DP-带49的数_哔哩哔哩_bilibili

    题目描述

    求区间 [1,n] 范围内包含多少带 49 的数。

    一个数是带 49 的数,当且仅当它的十进制表示中存在连续的两位,其中较高位为 4,较低位为 9。

    比如:49, 149, 1234987 都是带 49 的数;

    而 4, 12345, 94, 999444 都不是。

    输入描述

    输入一个整数 n (1 ≤ n < 2^63)。

    输出描述

    输出一个整数,表示区间 [1, n] 范围内存在多少带 49 的数。

    用例

    输入500
    输出15
    说明

    题目解析

    本题由于数量级太大,1 ≤ n < 2^63,因此不能用暴力枚举,只能用数位DP解决。

    本题解题思路和数位DP - 带3的数_伏城之外的博客-CSDN博客

    类似,只是数位特征从3变为了49。

    也就是说,我们需要关注两级,前一级取值4,后一级取值9,才能满足条件。因此记忆化数组f不仅需要记住某级i下含49数的个数,还需要记住前一级取值是什么,即f需要初始化为二维数组。

    f[i][pre],i表示当前级,pre表示上一级的取值。

    关于f[i][pre]的含义,可以看下代码注释,以及debug调试理解一下,这个东西已经超出语言的描述能力,只可意会不可言传。

    Java算法源码

    1. import java.util.Scanner;
    2. public class Main {
    3. public static void main(String[] args) {
    4. Scanner sc = new Scanner(System.in);
    5. int num = sc.nextInt();
    6. String tmp = num + "";
    7. int level = tmp.length();
    8. // 将数字num,如500,转化为整型数组[5, 0, 0]
    9. int[] arr = new int[level];
    10. for (int i = 0; i < level; i++) {
    11. arr[i] = Integer.parseInt(tmp.charAt(i) + "");
    12. }
    13. // 数位DP的核心优化:记忆化数组f
    14. int[][] f = new int[level][10];
    15. // 统计1~num范围内不含49数的个数
    16. int notContain49 = dfs(0, true, f, arr, 0);
    17. // 1~num范围内所有数的个数
    18. int total = num + 1;
    19. // 总数 - 不含49数的个数 = 含49数的个数
    20. System.out.println(total - notContain49);
    21. }
    22. // 注意dfs统计的是不含49的数
    23. public static int dfs(int p, boolean limit, int[][] f, int[] arr, int pre) {
    24. // p代表递归树的层级,树的层级又对应数位,比如树的第0层对应百位,第1层对应十位,第2层对应个位
    25. if (p == arr.length) return 1;
    26. // f[p][pre] 含义看43~45行注释,此行是复用逻辑,避免重复递归
    27. if (!limit && f[p][pre] > 0) return f[p][pre];
    28. // limit说明当前数位是否受限,比如500的百位取值会受限,只能取0~5,如果数位取值不受限,那么可取0~9,比如百位取4,则十位可取0~9
    29. // max是当前数位可取的最大值
    30. int max = limit ? arr[p] : 9;
    31. // count记录当前数位下不含49的数的个数
    32. int count = 0;
    33. // i 是当前层级的值
    34. for (int i = 0; i <= max; i++) {
    35. // 如果当前层级取值9,且前一层级取值4,那么当前数,及后续分支都是含49的数,不需要继续递归下去了
    36. if (i == 9 && pre == 4) continue;
    37. // 否则进入下一层,即p+1层
    38. count += dfs(p + 1, limit && i == max, f, arr, i);
    39. }
    40. // 如果当前数位取值不受限,则统计的不含49的数就具有复用性
    41. // 缓存: 如果p-1层取值pre,那么p层所有取值中不含49的数的个数; 比如可以debug理解一下,百位取值0时,完成缓存f[p][pre]:十位(p-1)取值0
    42. // (pre),那么个位(p)可以取0~9任意数,组成的数都不会含49,此时对应不含49数的个数是10个(f[p][pre])
    43. // 那么下次百位取1时,十位又取0了,此时就可以不用继续尝试个位取值了,可以直接复用f[2][0]的取值
    44. if (!limit) f[p][pre] = count;
    45. return count;
    46. }
    47. }

    JavaScript算法源码

    1. /* JavaScript Node ACM模式 控制台输入获取 */
    2. const readline = require("readline");
    3. const rl = readline.createInterface({
    4. input: process.stdin,
    5. output: process.stdout,
    6. });
    7. rl.on("line", (line) => {
    8. console.log(digitSearch(line));
    9. });
    10. function digitSearch(num) {
    11. const arr = num.split("").map(Number);
    12. const f = new Array(arr.length).fill(0).map(() => new Array(10));
    13. return num - 0 + 1 - dfs(0, true, f, arr, 0);
    14. }
    15. // 注意dfs统计的是不含49的数
    16. function dfs(p, limit, f, arr, pre) {
    17. // p代表递归树的层级,树的层级又对应数位,比如树的第0层对应百位,第1层对应十位,第2层对应个位
    18. if (p === arr.length) return 1;
    19. // f[p][pre] 含义看43~45行注释,此行是复用逻辑,避免重复递归
    20. if (!limit && f[p][pre]) return f[p][pre];
    21. // limit说明当前数位是否受限,比如500的百位取值会受限,只能取0~5,如果数位取值不受限,那么可取0~9,比如百位取4,则十位可取0~9
    22. // max是当前数位可取的最大值
    23. const max = limit ? arr[p] : 9;
    24. // count记录当前数位下不含49的数的个数
    25. let count = 0;
    26. // i 是当前数位的值
    27. for (let i = 0; i <= max; i++) {
    28. // 如果当前数位取值9,且前一数位取值4,那么当前数,及后续分支都是含49的数,不需要继续递归下去了
    29. if (i === 9 && pre === 4) continue;
    30. // 否则进入下一层,即p+1层
    31. count += dfs(p + 1, limit && i === max, f, arr, i);
    32. }
    33. // 如果当前数位取值不受限,则统计的不含49的数就具有复用性
    34. // 缓存: 如果p-1层取值pre,那么p层所有取值中不含49的数的个数; 比如可以debug理解一下,百位取值0时,完成缓存f[p][pre]:十位(p-1)取值0 (pre),那么个位(p)可以取0~9任意数,组成的数都不会含49,此时对应不含49数的个数是10个(f[p][pre])
    35. // 那么下次百位取1时,十位又取0了,此时就可以不用继续尝试个位取值了,可以直接复用f[2][0]的取值
    36. if (!limit) f[p][pre] = count;
    37. return count;
    38. }

    Python算法源码

    1. # 输入获取
    2. num = input()
    3. # 算法入口
    4. # 注意dfs统计的是不含49的数
    5. def dfs(p, limit, f, arr, pre):
    6. # p代表递归树的层级,树的层级又对应数位,比如树的第0层对应百位,第1层对应十位,第2层对应个位
    7. if p == len(arr):
    8. return 1
    9. # f[p][pre] 含义看43~45行注释,此行是复用逻辑,避免重复递归
    10. if not limit and f[p][pre] > 0:
    11. return f[p][pre]
    12. # limit说明当前数位是否受限,比如500的百位取值会受限,只能取0~5,如果数位取值不受限,那么可取0~9,比如百位取4,则十位可取0~9
    13. # maxV是当前数位可取的最大值
    14. maxV = arr[p] if limit else 9
    15. # count记录当前数位下不含49的数的个数
    16. count = 0
    17. # i 是当前层级的值
    18. for i in range(maxV + 1):
    19. # 如果当前层级取值9,且前一层级取值4,那么当前数,及后续分支都是含49的数,不需要继续递归下去了
    20. if i == 9 and pre == 4:
    21. continue
    22. # 否则进入下一层,即p+1层
    23. count += dfs(p + 1, limit and i == maxV, f, arr, i)
    24. # 如果当前数位取值不受限,则统计的不含49的数就具有复用性
    25. if not limit:
    26. # 缓存: 如果p-1层取值pre,那么p层所有取值中不含49的数的个数; 比如可以debug理解一下,百位取值0时,完成缓存f[p][pre]:十位(p-1)取值0
    27. # (pre),那么个位(p)可以取0~9任意数,组成的数都不会含49,此时对应不含49数的个数是10个(f[p][pre])
    28. # 那么下次百位取1时,十位又取0了,此时就可以不用继续尝试个位取值了,可以直接复用f[2][0]的取值
    29. f[p][pre] = count
    30. return count
    31. # 算法调用
    32. arr = list(map(int, list(num))) # 将数字num,如500,转化为整型数组[5, 0, 0]
    33. f = [[0] * 10 for _ in range(len(num))] # 数位DP的核心优化:记忆化数组f
    34. notContain49 = dfs(0, True, f, arr, 0)
    35. total = int(num) + 1 # 统计1~num范围内不含49数的个数
    36. print(total - notContain49) # 总数 - 不含49数的个数 = 含49数的个数
  • 相关阅读:
    MacBook 往服务器上传、下载文件的几种操作
    新一代开源语音库CoQui TTS冲到了GitHub 20.5k Star
    软考高级之系统架构师之软件工程
    C++【5】类与对象(一) 例子演示
    【linux学习】打包压缩与搜索命令
    免费SSL证书和付费SSL证书的区别在哪儿?
    快速介绍Scala特性
    GET 和 POST请求的区别是什么
    CSA研讨会 | 研讨零信任SASE安全架构,吉利控股首谈其部署方案
    golang结构与接口方法实现与交互使用示例
  • 原文地址:https://blog.csdn.net/qfc_128220/article/details/128139171