题目链接
求某个区间内满足某种性质的数的个数,常常用数位DP来解。数位DP常用俩技巧:
1、若能求得 d p ( n ) dp(n) dp(n)是 [ 0 , n ] [0, n] [0,n]满足该性质的数的个数,那么答案就是 d p ( Y ) − d p ( X − 1 ) dp(Y) - dp(X - 1) dp(Y)−dp(X−1)
2、设要求 d p ( N ) dp(N) dp(N), N N N从低到高每位分别是 a 0 , a 1 , a 2 , . . . , a n − 1 a_0, a_1, a_2, ..., a_{n-1} a0,a1,a2,...,an−1,用树来考虑问题
先预处理出f[i, j]
,代表位数是i
,最高位是j
,的所有数中不降数的个数。
例如f[3, 6]
就是600~699的不降数个数
f[i, j]
其实就是所有i-1位,最高位>=j的不降数之和
即f[i, j] = f[i-1, j] + f[i-1, j+1] + ... + f[i-1, 9]
注意这样求出的f[i, 0]
是包含前导0的,例如f[2, 0]
会等于10,意味着包含01、02、…、09十个数。
f[4, 0]
则会包括01、012、0123等1至3位不下降数。
但由于这题是求不下降序列,前导0不会影响答案,如果求的是不上升序列就不能包括前导0了。
不妨设要求 d p ( N ) dp(N) dp(N), N N N从低到高每位分别是 a 0 , a 1 , a 2 , . . . , a n − 1 a_0, a_1, a_2, ..., a_{n-1} a0,a1,a2,...,an−1
下面以 N = 2314 N=2314 N=2314为例,求 [ 0 , 2314 ] [0, 2314] [0,2314]中符合条件的数字数目。
res += f[4, 0] + f[4, 1]
res += f[3, 2]
本题代码
#include
#include
using namespace std;
const int N = 15;
int f[N][N];
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 - 1; j ++ ) { // 因为要求不降,所以从上一位a_{i+1}开始枚举
res += f[i + 1][j];
}
last = x;
if (!i) res ++; // 每一位都都比上一位小,n自身也是不降数,答案加上n
}
return res;
}
int main() {
init();
int a, b;
while(~scanf("%d%d", &a, &b)) {
printf("%d\n", dp(b) - dp(a - 1));
}
return 0;
}
数位DP模板如下:
int dp(int n) {
if (!n) return 0; // 非法边界
vector<int> nums;
while(n) nums.push_back(n % B), n /= B; // 将数按位分解
int res = 0; // 问题答案
int last = 0; // 向下走时父亲的信息(此题是使用了多少个1)
for (int i = num.size() - 1; i >= 0; i --) { // 最高位向最低位枚举
int x = nums[i];
if (!i) // n自身也符合条件,特判。相当于树最后一层的右结点x=A_0
}
return res;
}
int main() {
printf("%d\n", dp[Y] - dp[X - 1]);
return 0;
}