数位统计DP是一种模板性较强的DP套路题,主要用于对数字数位上的统计。在完成一些对数位上数字有明确要求的统计操作时,对区间内数字的暴力逐个枚举会产生大量无效工作,严重超时。
给定两个正整数 a a a 和 b b b,求在 [ a , b ] [a,b] [a,b] 中的所有整数中,每个数码(digit)各出现了多少次。
仅包含一行两个整数 a , b a,b a,b,含义如上所述。
包含一行十个整数,分别表示 0 ∼ 9 0\sim 9 0∼9 在 [ a , b ] [a,b] [a,b] 中出现了多少次。
1 99
9 20 20 20 20 20 20 20 20 20
从取值范围就可以看出,暴力绝对不可能完成。
先考虑一个较为简单的问题,如果区间为 [ 0 , 9 … 9 ⏟ i ] ( 1 ≤ i ≤ 11 ) [0,\underbrace{9\dots 9}_i](1\le i\le 11) [0,i 9…9](1≤i≤11),我们如何计算其中 3 3 3 出现的次数?
如果就是按位统计,直接通过组合数学的方法即可计算得出结果。
那么,如果最高位只是普通值应该怎么办呢?
以 0 ∼ 512 0\sim 512 0∼512 中 3 3 3 的出现次数为例,可以先对百位的 0 ∼ 4 0\sim 4 0∼4 进行枚举。
整百的数里,不算百位, 3 3 3 均出现 20 20 20 次;百位的 3 3 3 出现 100 100 100 次;这样就统计完了 0 ∼ 499 0\sim 499 0∼499,问题变为 500 ∼ 512 500\sim 512 500∼512 的子问题……
通常数位DP可以考虑两种方法:迭代和记忆化递归。这两种方式模板性都非常强,需要记住。
#include
const int N = 15;
using LL = long long;
LL f[N], cnta[N], cntb[N], ten[N];
int a[N];
// 求出 1~n的所有数中,各个数字出现的次数,存入cnt
void calc(LL n, LL cnt[]) {
LL t = n, len = 0;
while (t) a[++len] = t % 10, t /= 10;
for (int i = len; i; --i) {
// 记录每个数字在前i-1位中出现的次数
for (int j = 0; j <= 9; ++j)
cnt[j] += f[i-1] * a[i];
// 记录第i位上每个数字出现的次数,除 a[i]以外
for (int j = 0; j < a[i]; ++j)
cnt[j] += ten[i-1];
// 去掉 n 的最高位a[i],然后,累加最高位a[i]出现了 n+1 次
n -= ten[i-1] * a[i], cnt[a[i]] += n + 1;
cnt[0] -= ten[i-1];
}
}
int main() {
LL a, b;
scanf("%lld%lld", &a, &b);
ten[0] = 1;
for (int i = 1; i <= 12; ++i) {
f[i] = i * ten[i-1];
ten[i] = ten[i-1] * 10;
}
// 由a-1可知,a 最小应该是 1
calc(a-1, cnta), calc(b, cntb);
for (int i = 0; i < 10; ++i)
printf("%lld ", cntb[i] - cnta[i]);
}
// pos指当前搜到哪一位,当pos=0时,就返回当前搜到的值
// lead表示当前是不是有前导0的,有即为true
// limit表示当前搜索的这一位有没有限制,即不能超过原数这一数位上的数字,1表示有限制,0表示没有
// s表示当前已经统计了多少要查找的数字d
// d表示当前查找的数字是什么
// f[pos][s]表示在当前pos位已经出现了s个d的情况下,右面所有位中d的出现次数
#include
typedef long long LL;
LL f[17][17], dig[17], d;
LL dfs(int pos, LL s, bool lead, bool limit) {
if (!pos) return s;
if (!limit && !lead && f[pos][s]) return f[pos][s];
int end = limit ? dig[pos] : 9;
LL ans = 0;
for (int i = 0; i <= end; ++i)
ans += dfs(pos-1, s+((!lead||i) && (i==d)), lead&&!i, limit && i==end);
if (!limit && !lead) f[pos][s] = ans;
return ans;
}
LL calc(LL x) {
int cnt = 0;
while (x) dig[++cnt] = x % 10, x /= 10;
return dfs(cnt, 0, true, true);
}
int main() {
LL a, b;
scanf("%lld%lld", &a, &b);
for (int i = 0; i <= 9; ++i)
d = i, printf("%lld ", calc(b) - calc(a-1));
return 0;
}