数字游戏 - 计数 DP

· · 个人记录

题意

定义 不降数 为某数字各个数位上的数从高位到低位均为非降。指定一个整数区间 [a,b],求这个区间内有多少不降数。

## 思路 题意十分清晰,暴力显然不允许。 这道题需要用到 **计数 DP** 来解决。 所谓的 **计数 DP**,是指在一段区间内 DP 以统计某种符合条件的数的问题。执行计数 DP 时一般先把区间划分成几个部分,通过初始化使一些特殊部分得到求解,再从这些转移出其他的状态。 这道题求的是不降数。通常计数 DP 的状态定义为 $f_{n,n}$,$n$ 为最大的位数。$f_{i,j}$ 表示如果这个数是 $i$ 位数,最高位为 $j$,到这个数共有多少满足的数。 先来看初始化。只有 $1$ 位的数自然是不降数。其他位枚举 $i,j$,每次枚举到从 $i-1$ 转移。很显然,**$j$ 以下的数一旦加入,构成的数就不是不降数了**。所以加入的数必须是 $\ge j$ 的。直接从 $j$ 枚举 $k$ 即可。 初始化的状态转移方程就是: $$ \left\{\begin{matrix}f_{1,i}=1\\f_{i,j}=\sum_{k=j}^{10} f_{i-1,k}\end{matrix}\right. $$ 还需要有一个能求出 $[0,x]$ 区间内有多少个不降数的函数 $calc$。把 $x$ 的各个数位分离,放入一个数组内。统计两个变量 $res$ 和 $last$,分别表示答案和上一位是几(动态维护 $last$)。从高到低遍历数位,第二维从 $last$ 开始一直遍历到 $a_i$ 结束,用 $res$ 加上区间内的不降数数量,实时修改 $last$ 变量,最后返回 $res$ 即可。 最后类似于求前缀和,答案是 $calc(r) - calc(l-1)$。 **Code** ```c++ #include<bits/stdc++.h> using namespace std; #define TRACE 1 #define tcout TRACE && cout #define int long long #define endl '\n' #define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); const int N = 12, M = 2e6+10; const int INF = 0x3f3f3f3f3f3f3f3f; //预处理 //0000-0999 //... int f[N][N]; //如果这个是 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 < 10; j ++){ for(int k = j; k < 10; k ++){ f[i][j] += f[i-1][k]; } } } } int calc(int x){ //0~x间有多少个 if(x == 0) return 1; int a[N] = {0}; int n = 0; while(x){ a[++n] = x % 10; x /= 10; } int res = 0; int last = 0; //上一位是几 for(int i = n; i >= 1; i --){ if(a[i] < last) break; for(int j = last; j < a[i]; j ++){ res += f[i][j]; } last = a[i]; if(i == 1) res++; } return res; } signed main(){ init(); int l, r; while(cin >> l >> r){ cout << calc(r) - calc(l-1) << endl; } return 0; } ```