数位dp从入门到lv1

颇为特别的dp,其他dp都是O(1)查询,这个查询还要写一大堆

 

做法一:

先用常规dp的方法,预处理出dp[ i ][ j ],dp[ i ][ j ]表示的是第 i 位数是 j 时,符合条件的数的个数,例如 dp[7][2] 表示的是形如 2XXXXXX 的数一共有多少是符合条件的

这个dp是容易递推的

 

然后我们要算小于等于某个数N的情况 

windy数 做法一

例: N=365734

把答案分成三部分

第一部分:位数和N一样, res1 = 1XXXXX + 2XXXXX = dp[ 6 ][ 1 ] + dp[ 6 ][ 2 ]

第二部分:位数<N的位数,res2 = 1XXXX + 2XXXX + ... + 9XXXX +1XXX + ... + 9XXX ... = ∑dp[ i ][ j ] (1 <= i < len(N) , 1 <= j <= 9)

第三部分:位数和N一样,且最高位和N一样,也就是3XXXXX (XXXXX <= 65734)的情况,

res3 = 30XXXX + 31XXXX + 35XXXX + 360XXX + 361XXX + ... + 364XXX = dp[ 5 ][ 0 ] + dp[ 5 ][ 1 ] + dp[ 5 ][ 5 ] + dp[ 4 ][ 0 ] +...+ dp[ 4 ][ 4 ];

注: 32XXXX,33XXXX,34XXXX不满足windy数的条件, 365XXX不满足条件,3650XX,3651XX....也就通通不满足条件了

#include<iostream>
#include<algorithm>
using namespace std;
int dp[13][10];
int ABS(int x) {
	return x >= 0 ? x : -x;
}
void pre() {
	for (int j = 0; j <= 9; j++) dp[1][j] = 1;
	for (int i = 2; i <= 10; i++) {
		for (int j = 0; j <= 9; j++) {
			for (int k = 0; k <= 9; k++) {
				if (ABS(j - k) >= 2) dp[i][j] += dp[i - 1][k];
			}
		}
	}
}
int qiu(int x) {
	if (x == 0) return 0;
	int b[12], len = 0;
	int res = 0;
	while (x) {
		b[++len] = x % 10;
		x /= 10;
	}
	for (int j = 1; j < b[len]; j++) res += dp[len][j];//第一部分
	for (int i = 1; i < len; i++) {//第二部分
		for (int j = 1; j <= 9; j++) {
			res += dp[i][j];
		}
	}
	bool flag = true;
	for (int i = len - 1; i; i--) {//第三部分
		for (int j = 0; j < b[i]; j++) {
			if (ABS(j - b[i+1]) >= 2) res += dp[i][j];
		}
		if (ABS(b[i + 1] - b[i]) < 2) {
			flag = false;
			break;
		}
	}
	if (flag) res++;
	return res;
}
int main()
{
	pre();
	int a, b;
	cin >> a >> b;
	cout << qiu(b) - qiu(a - 1) << endl;
	return 0;
}

  

做法一总结 : 先预处理dp数组,然后把ans分为那三个部分,根据题意改写,第三部分受题意的影响最大,要灵活变化

做法二:

用记忆化搜索的方法,从最高位开始记忆化搜索,用dp[ pos ][ pre ][ lim ][ zero ]记忆化,pos表示是第几位,pre表示第pos + 1位填了哪个数,lim表示第pos位是否有上界限制,zero表示第pos位是否不能取0

windy数 做法二

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int dp[12][10][2][2]; // dp[pos][pre][lim][zero]
int b[12],len;
int dfs(int pos, int pre, bool lim, bool zero) {
	if (~dp[pos][pre][lim][zero]) return dp[pos][pre][lim][zero];
	if (!pos) return dp[pos][pre][lim][zero] = 1;//pos==0说明搜到底了,在此题中,搜到底表示搜到了一个合法的数。
	int res = 0;
	int r = lim ? b[pos] : 9;
	int l = zero ? 1 : 0;
	for (int i = l; i <= r; i++) {
		if (abs(i - pre) < 2) continue;
		res += dfs(pos - 1, i, lim && i == r, 0);//不是最高位,可以取0
	}
	return dp[pos][pre][lim][zero] = res;//记忆化
}
int qiu(int x) {
	if (x == 0) return 0;
	int res = 0;
	memset(dp, -1, sizeof(dp));//初始化
	len = 0;
	do {
		b[++len] = x % 10;
		x /= 10;
	} while (x);
	res += dfs(len, 11, 1, 1);//最高位有上界限制,且不能取0
	for (int i = len - 1; i; i--) {
		res += dfs(i, 11, 0, 1);//最高位无上界限制,不能取0
	}
	return res;
}
int main()
{
	int a, b;
	cin >> a >> b;
	cout << qiu(b) - qiu(a - 1) << endl;
	return 0;
}

  

记忆化搜索的做法在数位dp中很实用

洛谷P4124 [CQOI2016]手机号码

这题条件很多,但是用记忆化搜索开个多维数组,就好写了。

 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
long long dp[13][10][10][2][2][2][2][2];//dp[pos][pre][prpre][lim][zero][three][_4][_8]
int b[13];
int len = 0;
long long dfs(int pos, int pre,int prpre, bool lim, bool zero, bool three, bool _4, bool _8) {
	if (_4 && _8) return 0;
	if (~dp[pos][pre][prpre][lim][zero][three][_4][_8]) return dp[pos][pre][prpre][lim][zero][three][_4][_8];
	if (!pos) return dp[pos][pre][prpre][lim][zero][three][_4][_8] = three;
	int r = lim ? b[pos] : 9;
	int l = zero ? 1 : 0;
	long long res = 0;
	for (int i = l; i <= r; i++) {
		res += dfs(pos - 1, i, pre,lim && i == r, 0, three||(pre == prpre && i == pre), _4 || i == 4, _8 || i == 8);
	}
	return dp[pos][pre][prpre][lim][zero][three][_4][_8] = res;
}
long long qiu(long long x) {
	memset(dp, -1, sizeof(dp));
	if (x < 1e10) return 0;
	len = 0;
	do{
		b[++len] = x % 10;
		x /= 10;
	} while (x);
	long long res = 0;
	res += dfs(len, 11, 12, 1, 1, 0, 0, 0);
	return res;
}
int main()
{
	long long a, b;
	cin >> a >> b;
	cout << qiu(b) - qiu(a - 1) << endl;
	return 0;
}

  

posted @ 2020-10-18 13:06  beta_dust  阅读(115)  评论(0编辑  收藏  举报