数位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] : 被分解数位的上限
posted @ 2025-03-11 22:43  nightmare_lhh  阅读(17)  评论(0)    收藏  举报