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