数位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;
}
}