前言
本篇博客目的是给自己总结复习用,而不是给萌新学习用滴。萌新能不能看得懂需要看造化=.=,毕竟数位 d p dp dp 还是有点难理解的。
概括
数位 d p dp dp,是用来解决一些带有数位上的限制的计数问题的。常见的有问区间 [ L , R ] [L, R] [L,R] 中满足某种数位限制的数的个数,或者求数对的对数等等,也可以用来求和。一般是用 [ 0 , R ] [0,R] [0,R] 的 值减去 [ 0 , L − 1 ] [0, L- 1] [0,L−1] 的值。
常常设 d p [ p o s ] [ s t a ] dp[pos][sta] dp[pos][sta] 表示从高到低来到了第 p o s pos pos 位,前面的状态为 s t a sta sta 的数的个数。
在 d f s dfs dfs 过程中,会有一个参数 l i m i t limit limit,表示当前数位的枚举范围是不是有限制。
只有当前面的每个数位都取到最大值时, l i m i t limit limit 才会等于 1 1 1。
l i m i t limit limit 等于 0 0 0 的时候,说明从这一位往后,每个数位都可以取 [ 0 , 9 ] [0, 9] [0,9],这里产生了大量重复或等价的状态,通过记忆化来压缩起来。
数位 d p dp dp 的时间复杂度为 O ( 状 态 数 ∗ 转 移 复 杂 度 ) O(状态数 * 转移复杂度) O(状态数∗转移复杂度)。
数位 d p dp dp 的模板
数位 d p dp dp 的 d p dp dp 数组可以分为记录 l i m i t limit limit 和不记录的,当统计的是数对的时候,记录 l i m i t limit limit 会使复杂度降低。
状态中带有 l i m i t limit limit 的
这种 d p dp dp 数组每次都要进行 m e m s e t memset memset,因为每个数的数位限制不同。
状态中不带 l i m i t limit limit 的
这种 d p dp dp 数组可以适用于多组数据,因为状态是普遍的,不是针对单独一个限制的。
入门题 [hdu2089]不要62
求 [ L , R ] [L,R] [L,R] 中数字中不带 4 4 4 和 62 62 62 的数的个数。
d f s dfs dfs 的过程中,传入几个参数:当前到了第几位,前面一位是不是 6 6 6,当前数位有没有限制。
#include <bits/stdc++.h>
using namespace std;
int dp[10][2], a[10];
int dfs(int pos, int f1, int f2){
if(!pos) return 1;
if(!f2 && dp[pos][f1] != -1) return dp[pos][f1];
int res = 0, up = f2? a[pos]: 9;
for(int i = 0; i <= up; i++){
if(i == 4 || (f1 && i == 2)) continue;
res += dfs(pos - 1, i == 6, f2 && i == up);
}
if(!f2) dp[pos][f1] = res;
return res;
}
int f(int x){
int tot = 0;
while(x) a[++tot] = x % 10, x /= 10;
return dfs(tot, 0, 1);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(dp, -1, sizeof(dp));
int l, r;
while(cin >> l >> r){
if(!l && !r) break;
cout << f(r) - f(l - 1) << '\n';
}
return 0;
}
[hdu3555] bomb
求 [ L , R ] [L,R] [L,R] 中包含 49 49 49 的数的个数。
f 1 f_1 f1 表示上一位是不是 4 4 4, f 2 f_2 f2 表示枚举是不是有限制, f 3 f_3 f3 表示前面是否已经出现 49 49 49 了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void debug_out(){
cerr << endl;
}
template<typename Head, typename