数位DP学习笔记
从头开始学习数位DP!
例一 Acwing310 启示录
https://www.acwing.com/problem/content/312/
先预处理出 \(f\) 数组。
\(f[i,3]\) 表示 \(i\) 位数中有多少个魔鬼数。
\(f[i,0/1/2]\) 表示 \(i\) 位数中有 \(0/1/2\) 个连续的6的非魔鬼数的个数。
有
\[f[i,0]=9\times(f[i-1,0]+f[i-1,1]+f[i-1,2])\\
f[i,1]=f[i-1,0]\\
f[i,2]=f[i-1,1]\\
f[i,3]=f[i-1,2]+10\times f[i-1,3]
\]
有了 \(f\) 数组,我们该怎样求出第 \(X\) 个魔鬼数呢?
考虑试填法,即从小到大依次确定每一位数字。
具体实现见代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
long long f[22][4];
inline void prework(void) {
f[0][0] = 1;
for (int i = 1; i <= 20; ++ i) {
f[i][0] = 9 * (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]);
f[i][1] = f[i - 1][0];
f[i][2] = f[i - 1][1];
f[i][3] = f[i - 1][2] + 10 * f[i - 1][3];
}
}
int main() {
prework();
int T = read();
while (T --) {
int n = read();
int m;
for (m = 0; f[m][3] < n; ++ m);
for (int i = m, k = 0; i >= 0; -- i) {
for (int j = 0; j <= 9; ++ j) {
long long cnt = f[i - 1][3];
if (j == 6 || k == 3) {
for (int l = max(0, 3 - k - (j == 6)); l < 3; ++ l)
cnt += f[i - 1][l];
}
if (cnt < n) {
n -= cnt;
}
else {
if (k < 3) {
if (j == 6) ++ k;
else k = 0;
}
printf("%d" ,j);
break;
}
}
}
puts("");
}
return 0;
}
例二 Acwing311 月之谜
https://www.acwing.com/problem/content/313/
设 \(f[i,j,k,l]\) 表示由 \(i\) 位数字构成、各位数字之和是 \(j\)、对 \(k\) 取模余数是 \(l\) 的数有多少个。
有
\[F[i,j,k,l]=\sum_{p=0}^9F[i-1,j-p,k,(l-p*10^{i-1})\bmod k]
\]
采取试填法的思想,只要填了一个比 \(R\) 要小的数字,那么后面的数无论是多少,整个数值都不会超过 \(R\)。如果填了一个正好是 \(R\) 那一位的数字,就继续向后枚举接下来的每一位。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
int f[13][91][91][91];
int pwr[11][83];
void prework(void) {
for (int k = 1; k <= 82; ++ k) {
pwr[0][k] = 1 % k;
for (int i = 1; i <= 10; ++ i) pwr[i][k] = pwr[i - 1][k] * 10 % k;
f[0][0][k][0] = 1;
for (int i = 1; i <= 10; ++ i) {
for (int j = 0; j <= 82; ++ j) {
for (int l = 0; l < k; ++ l) {
for (int p = 0; p <= 9; ++ p) {
if (j - p < 0) break;
f[i][j][k][l] += f[i - 1][j - p][k][((l - p * pwr[i - 1][k]) % k + k) % k];
}
}
}
}
}
}
inline int count(int R) {
int a[11], n;
for (n = 0; R; R /= 10) a[++ n] = R % 10;
int ans = 0;
for (int sum = 1; sum <= 82; ++ sum) {
int t = 0;
int q = 0;
for (int i = n; i; -- i) {
for (int p = 0; p < a[i]; ++ p) {
if (sum - t - p < 0) break;
ans += f[i - 1][sum - t - p][sum][((-q - p * pwr[i - 1][sum]) % sum + sum) % sum];
}
t += a[i];
q = (q + a[i] * pwr[i - 1][sum]) % sum;
}
if (t == sum && q == 0) ++ ans;
}
return ans;
}
int main() {
prework();
int L = read(), R = read();
cout << count(R) - count(L - 1) << endl;
return 0;
}