【刷题】动态规划——数位DP:数字游戏

【刷题】动态规划——数位DP:数字游戏_第1张图片

题目链接

求某个区间内满足某种性质的数的个数,常常用数位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(X1)
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,...,an1,用树来考虑问题

【刷题】动态规划——数位DP:数字游戏_第2张图片

先预处理出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,...,an1

下面以 N = 2314 N=2314 N=2314为例,求 [ 0 , 2314 ] [0, 2314] [0,2314]中符合条件的数字数目。

  • a n − 1 = a 3 = 2 a_{n-1}=a_{3} = 2 an1=a3=2,分成两区间[0, 1999],[2000, 2314]
    • 区间[0, 1999]:res += f[4, 0] + f[4, 1]
    • 区间[2000, 2314]:看 a n − 2 = a 2 = 3 a_{n-2} = a_{2}=3 an2=a2=3
  • a n − 2 = a 2 = 3 a_{n-2}=a_{2} = 3 an2=a2=3,分成两区间[2000, 2299],[2300, 2314]
    • 区间[2000, 2299]:相当于求[0, 299]的不降数个数,由于上一位是2,所以只能算[200, 299]的不降数res += f[3, 2]
    • [2300, 2314]:1<3,不存在不降数,退出

本题代码

#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;
}

你可能感兴趣的:(刷题,动态规划,算法)