数位DP--P2602--[ZJOI2010]数字计数 java实现

题目

题目链接:

P2602 [ZJOI2010]数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

给定两个正整数 aa 和 bb,求在 [a,b][a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

输入格式

仅包含一行两个整数 a,ba,b,含义如上所述。

输出格式

包含一行十个整数,分别表示 0∼9 在 [a,b] 中出现了多少次。

输入输出样例

输入:

1 99

输出:

9 20 20 20 20 20 20 20 20 20

说明/提示

数据规模与约定

\[对于 30\%的数据,保证 a\le b\le10^{6}\\ 对于 100\%的数据,保证 1\le a\le b\le 10^{12} \]

数位DP大体思路&模板

数位DP--P2657--Windy数 java实现 - MonstroのBlog (reclusiveone.com)

数位DP--P2657--Windy数 java实现 - Monstro - 博客园 (cnblogs.com)

当前题目注意点

相对于Windy数这个模板之外,有一些不太一样的地方。

  • 题目没有与相邻数字的关系要求,所以,dfs中可以不记录前一个数字
  • 题目需要计算每一位的出现次数,相比于windy数,条件判断实际上简单了一些
  • 计算每一位出现的次数,dp数组这样设置:dp[当前所在的位置][当前位置下,某个数字出现的次数]
  • 最终需要累计所有位置,目标数字出现的结果,作为最终结果。

代码与windy数是十分相似的。具体逻辑可以根据代码注释看一下。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    //记录输入数字的每一位
    static long[] positionNumber;
    //记忆化搜索需要的数组
    static long[][] dp;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        long a = Long.parseLong(st.nextToken());
        long b = Long.parseLong(st.nextToken());

        //通过getCnt(number)获取0~9位出现的次数。索引0~9分别代表数字0~9。
        long[] resultA = getCnt(a - 1);
        long[] resultB = getCnt(b);
        StringBuilder bu = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            bu.append(resultB[i] - resultA[i]).append(" ");
        }
        System.out.println(bu.toString());
    }

    private static long[] getCnt(long number) {
        //按照题目要求的最大数字位数设置即可。
        positionNumber = new long[14];
        int index = 0;
        while (number > 0) {
            positionNumber[index++] = number % 10;
            number /= 10;
        }
        long[] result = new long[10];
        //遍历0~9,分别计算数字0~9在数字中出现的次数。
        for (int i = 0; i < 10; i++) {
            //dp[i][j] 表示,当前位置是i,并且这一位上数字target出现j次情况下,这一类数字中,target出现的总次数
            dp = new long[14][20];
            //初始化dp数组为-1,为了后面能够判断当前状态是否有被记录过。
            for (int j = 0; j < dp.length; j++) {
                Arrays.fill(dp[j], -1);
            }
            result[i] = dfs(index - 1, 0, true, true, i);
        }
        return result;
    }

    /**
     * dfs计算目标数字target在当前数字中出现的次数。
     * @param currentPosition 当前的位置
     * @param sum 当前位置下,数字target出现的次数
     * @param isLimit 前面的数字位是否都填写了,不超出给定范围的最大值
     * @param lead 是否含有前导零
     * @param target 需要检测的目标数字
     * @return
     */
    private static long dfs(int currentPosition, int sum, boolean isLimit, boolean lead, int target) {
        //遍历完毕最后一位
        if (currentPosition < 0) {
            return sum;
        }
        /*
        记忆化搜索
        如果不是特殊情况,并且dp[currentPosition][sum]已经记录过,就没必要再计算一次。
        特殊情况是:有前导零并且当前位置之前的位置填写了最大值(这样就会使得当前位置不能够填写0~9)【具体讲解可以看windy数】
         */
        if (!isLimit && !lead && dp[currentPosition][sum] != -1) {
            return dp[currentPosition][sum];
        }
        long result = 0;
        int temp = 0;
        //表示当前位置上,最多能够填写到多少
        //例如:如果输入为12345,对于12???,第三位能够最多填写到3.对于11???,第三位最多能够填写到9.
        long maxNum = isLimit ? positionNumber[currentPosition] : 9;
        for (int i = 0; i <= maxNum; i++) {
            //用来计算这一位的填写,是否要计算到sum总数中
            temp = 0;
            //只有填写的数字和目标数字相同,才可能记录
            if (target == i) {
                /*
                如果当前位置是0,并且没有前导零的时候,这个0才能够被计入出现次数
                举例:
                10123:第二位0,能够被算作0出现的次数
                00123:第二位0,不能够被算作0出现的次数
                */
                if (i == 0 && !lead) {
                    temp = 1;
                }
                //0的情况排除后,非0的情况,是一定要被计入出现次数的
                else if (i != 0) {
                    temp = 1;
                }
            }
            /*
            currentPosition-1:进行下一位的搜索
            sum+temp:当前位置下,填入了i后,target数字在这一位出现的次数
            isLimit && (i == maxNum):只有在前面的位置都填写了最大值(isLimit为true),并且当前位置也变成了最大值的时候,对于下一位来说,isLimit才能是true
            lead && (i == 0):类似与isLimit。只有当前数字有前导零,并且当前这一位填写了0,对于下一位来说,lead才能使true
            target:搜索的目标数字
            sum:只表示当前位置上,target出现的次数
            result:这里进行累加,作为最终的计算结果
             */
            result += dfs(currentPosition - 1, sum + temp, isLimit && (i == maxNum), lead && (i == 0), target);
        }
        //对于非特殊情况,记录下来
        if (!isLimit && !lead) {
            dp[currentPosition][sum] = result;
        }
        //最终结果
        return result;
    }
}
posted @ 2021-08-05 10:12  Monstro  阅读(95)  评论(0)    收藏  举报