数位DP总结
数位\(DP\)
经过一天半的苦苦挣扎,真的很感谢\(Acwing \,\, yxc\)的讲解,终于感觉自己入门一些了。
数位\(DP\)通常是给定一个\([L,R]\)区间,让你求区间中满足题目要求的数的个数,然后我们只需要利用前缀和思想,分别求出\([0, R]\)和\([0, L]\)的满足要求的数的个数,设\(f(r)\)为\([0, R]\)的满足要求的数的个数,设\(f(l)\)为\([0, l]\)的满足要求的数的个数,然后\(f(r) - f(l - 1)\)即可。
原来数位\(DP\)也有模板,模板如下。
void init() {
}
int dp(int n) {
if (!n) return ?; //通常是特判掉n为0的情况,如果n = 0满足题目要求就返回1,否则返回0
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10; //取出所有数位
int res = 0, last = 0; //last记录上一位数字是啥玩意
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = 0; j < x; j++) {
if (限制条件) continue;
res += f[i + 1][j];
}
if (限制条件) break;
last = x;
if (!i && 限制条件满足要求) res++;
}
return res;
}
int main() {
while (cin >> l >> r, l) {
init();
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}
还有就是根据题目要求预先处理出\(DP\)数组,这个\(DP\)很灵活,可能是就让预处理一个组合数,也可能是一个其他类型的\(DP\),这也是数位\(DP\)的难点。
下面是做的几道题目:
AcWing 1081. 度的数量
因为这个题要求填非\(0\),即\(1\),我们就需要预处理出一个组合数,然后\(last\)维护的是前边已经出现几个\(1\)了, \(f[i][j]\)表示从\(i\)个数中选\(j\)个的方案的集合。
#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int f[N][N];
int l, r;
int k, b;
void init() {
for (int i = 0; i < N; i++) {
for (int j = 0; j <= i; j++) {
if (!j) f[i][j] = 1;
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
}
int dp(int n) {
if (!n) return 0;
vector<int> nums;
while (n) nums.push_back(n % b), n /= b;
int res = 0, last = 0;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
if (x) {
res += f[i][k - last]; //如果当前这位为0
if (x > 1) { //根据上面推出的情况 非1即0,出现x大于1,那么剩下的位数就不用看了。
if (k - last - 1 > 0) res += f[i][k - last - 1];
break;
} else if (x == 1) {
last++;
if (last > k) break;
}
}
if (!i && last == k) res++; //最后右边分支的一种,n本身就是一个合法数字
}
return res;
}
int main() {
init();
cin >> l >> r;
cin >> k >> b;
cout << dp(r) - dp(l - 1) << endl;
return 0;
}
AcWing 1082. 数字游戏
这个数要求是不降数,那么我们可以把\(f[i][j]\)设成一共有\(i\)位,且最高位为\(j\)的不降数的集合,因为要求数字是单调非减的,那么我们\(last\)维护的就是上一位数字是几,当前数字\(x\)一定不能小于它。
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
int f[N][N]; //表示一共有i位,且最高位填j的方案
void init() {
for (int i = 0; i <= 9; i++) f[1][i] = 1; //一共有1位,先预处理
for (int i = 2; i < N; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = j; k <= 9; k++) {
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n) {
if (!n) return 1;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0;
int last = 0;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
if (x < last) break;
for (int j = last; j < x; j++) {
res += f[i + 1][j];
}
last = x;
if (!i) res++;
}
return res;
}
int main() {
int l, r;
init();
while (cin >> l >> r) {
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}
AcWing 1083. Windy数
要求数位间两个数字之差至少位2,那么\(f[i][j]\)表示为设成一共有\(i\)位,且最高位为\(j\)的Windy数的集合,\(last\)维护的就是上一位数字是几,要求\(abs(x - last) >= 2\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
int f[N][N]; //f[i][j]表示有i位,且最高位为j的集合
void init() {
for (int i = 0; i <= 9; i++) f[1][i] = 1;
for (int i = 2; i < N; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
if (abs(j - k) >= 2) f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n) {
if (!n) return 0;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 12;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = i == nums.size() - 1; j < x; j++) {
if (abs(j - last) >= 2)
res += f[i + 1][j];
}
if (abs(x - last) >= 2) last = x;
else break;
if (!i) res++;
}
//处理含前导0的情况
for (int i = 1; i < nums.size(); i++) {
for (int j = 1; j <= 9; j++) {
res += f[i][j];
}
}
return res;
}
int main() {
int l, r;
init();
cin >> l >> r;
cout << dp(r) - dp(l - 1) << endl;
return 0;
}
AcWing 1084. 数字游戏 II
题目要求数字的各位之和\(Mod \,\, P\)为0,\(f[i][j][k]\)表示一共有\(i\)位,且最高位为\(j\),数位和\(Mod \,\,P\,=\,k\)的数的集合,\(last\)表示前边的数位和。
设\(nums\)里从低到高存了一个数\(n\)的各位数字,所以倒序枚举,\(x\)表示当前枚举到的那一位,对于题目的要求需满足,\((a_{n - 1} + a_{n - 2} + x + ... +()) \,\, Mod \,\,N = 0\),我们用\(last\)记录前边位数的和,\(last = a_{n - 1} + a_{n - 2}\),那么再\(DP\)的过程根据上面的要求,我们需要满足:\((x + ... + ()) \,\, Mod \,\, N = -last \,\, Mod \,\, N\),注意因为\(C++\)的特性,负数取模还是负数,所以我们可以这样做,把其取模后的数转成正的。
int mod(int x, int y) {
return (x % y + y) % y;
}
// Problem: 数字游戏 II
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1086/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int N = 11, M = 110;
int f[N][10][M]; //一共有i位且最高位为j的且数位和 Mod P = k的集合
int l, r, P;
int mod(int x, int y) {
return (x % y + y) % y;
}
void init() {
memset(f, 0, sizeof f);
for (int i = 0; i <= 9; i++) f[1][i][i % P]++;
for (int i = 2; i < N; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k < P; k++) {
for (int x = 0; x <= 9; x++) {
f[i][j][k] += f[i - 1][x][mod(k - j, P)];
}
}
}
}
}
int dp(int n) {
if (!n) return 1;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 0;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = 0; j < x; j++) res += f[i + 1][j][mod(-last, P)];
last += x;
if (!i && last % P == 0) res++;
}
return res;
}
int main() {
while (cin >> l >> r >> P) {
init();
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}
AcWing 1085. 不要62
这个题目就可以独立做出来了,不要\(4\)和不要\(62\),那\(f[i][j]\)表示为设成一共有\(i\)位,且最高位为\(j\)的数的集合,用\(last\)表示上一位数,用\(x\)表示当前枚举的数,如果\(last = 6\) 并且 \(x = 2\)或者\(x = 4\)就是不符合条件的。
// Problem: 不要62
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1087/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
const int N = 11;
int f[N][N]; //一共有i位,并且最高位为j的方案集合
int l, r;
void init() {
memset(f, 0, sizeof f);
for (int i = 0; i <= 9; i++) {
if (i != 4) f[1][i] = 1;
}
for (int i = 2; i < N; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
if (j == 4 || k == 4) continue;
if (j == 6 && k == 2) continue;
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n) {
if (!n) return 1;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 0; //last记录上一位数字是啥玩意
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = 0; j < x; j++) {
if ((j == 4) || (last == 6 && j == 2)) continue;
res += f[i + 1][j];
}
if (x == 4 || last == 6 && x == 2) break;
last = x;
if (!i) res++;
}
return res;
}
int main() {
while (cin >> l >> r, l) {
init();
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}