数位DP
数位DP
概念
数位DP是用来处理与数位有关的区间统计问题的,其基本思路是将数位作为一个个阶段进行处理
算法实现
我们先来看看一道例题:
给定两个正整数 \(a\) 和 \(b\),求在 \([a,b]\) 中的所有整数中,每个数码各出现了多少次(\(1 \le a \le b \le 10^{12}\))
例:\([1,14]\) 中,有数字 \(1,2,3,4,5,6,7,8,9,10,11,12,13,14\),其中数码 \(0\) 到 \(9\) 各出现 \(1,6,2,2,2,1,1,1,1,1\) 次
显然,我们无法通过枚举 \([a,b]\) 中的每一个数字解决问题。我们考虑枚举数位并加以记忆化搜索解决。
这时,我们需要维护什么:
-
枚举到了第几个数位 \(pos\) ,如本道题共有 \(12\) 个数位
-
是否到达极限 \(limit\) :
假设 \(b=147\) ,那么我们只能最高位只能枚举到 \(1\)。这时,我们记录一下之前是否到达极限(从左向右枚举,即最高位是否超出极限),如果上几位超出极限且这一位也超出极限,那么记录超出极限
-
是否有前导\(0\) \(is0\)(前面的位全为 \(0\)):
如果前面所有位都是前导\(0\) 且这一位也是 \(0\),那么标记前导\(0\)
统计答案时应跳过前导\(0\)
-
以上是数位DP模板需要,下面是本题统计的基本数据:
- 当前统计的数位 \(x\in [0,9]\)
- 当前路径上该数位 \(x\) 的答案 \(k\)(\(x\) 的个数)
ll dfs(bool limit,bool is0,int pos,int x,int k) { // 统计数字x出现次数
// 是否到达极限 是否为前导0 当前数位 统计数字 当前路径答案
if (pos == 0) return k; // 到达边界
if (!limit && !is0 && dp[pos][k] != -1) return dp[pos][k]; // 记忆化搜索
int t = (limit ? num[pos] : 9); // 该位枚举上限
ll ans = 0; // 答案
for (int i = 0;i <= t;i ++) { // 枚举可能数字
if (is0) ans += dfs( // 前导零
limit && i == t, // 高位为极限且这一位到达极限
is0 && i == 0, // 高位为前导0且这一位为0
pos - 1, // 下一数位
x,
k + (i == x && i != 0) // 如果该位不为0,统计。反之不统计
);
else ans += dfs(limit && i == t,is0,pos - 1,x,k + (i == x)); // 同上
}
if (!limit && !is0) dp[pos][k] = ans; // 记录答案
return ans;
}
dp[pos][k] : pos位置答案为k
num[pos] : 被分解数位的上限

浙公网安备 33010602011771号