1. 程式人生 > 實用技巧 >數位dp從入門到lv1

數位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;
}