在了解數位dp之前,先來看一個問題:
例1.求a~b中不包含49的數的個數. 0 < a、b < 2*10^9
注意到n的數據范圍非常大,暴力求解是不可能的,考慮dp,如果直接記錄下數字,數組會開不起,該怎么辦呢?要用到數位dp.
數位dp一般應用於:
求出在給定區間[A,B]內,符合條件P(i)的數i的個數.
條件P(i)一般與數的大小無關,而與 數的組成 有關.
這樣,我們就要考慮一些特殊的記錄方法來做這道題.一般來說,要保存給定數的每個位置的數.然后要記錄的狀態為當前操作數的位數,剩下的就是根據題目的需要來記錄.可以發現,數位dp的題做法一般都差不多,只是定義狀態的不同罷了.
下面開始針對例題進行分析:
我們要求[a,b]不包含49的數的個數,可以想到利用前綴和來做,具體來說,就是[a,b] = [0,b] - [0,a),(")"是不包括a),我們先求出給定a,b的每個位置的數,保存在數組s中,例如a = 109,那么a[1] = 9,a[2] = 0,a[3] = 1.然后開始dp,我們可以選擇記憶化搜索或者是遞推,前一種相對於第二種而言簡單和較為容易理解一些,所以我們選擇記憶化搜索.那么需要記錄些什么呢?首先長度是一定要記錄的,然后記錄當前的數位是否為4,這樣就便於在記憶化搜索中得到答案.
然后進行記憶化搜索,記錄上一位是否為4和枚舉這一位,如果沒有限制的話很好辦,直接枚舉就可以了,但是這樣可能會超空間,因此我們每次都必須要判斷是否有最大的限制,這里不是很好說,看代碼更容易理解:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int a, b, shu[20], dp[20][2]; int dfs(int len, bool if4, bool shangxian) { if (len == 0) return 1; if (!shangxian && dp[len][if4]) //為什么要返回呢?可以畫圖理解當我們搜到3XXX時,程序運行到1XXX時就已經把3XXX之后的搜索完了,記憶化也是這個用意. return dp[len][if4]; int cnt = 0, maxx = (shangxian ? shu[len] : 9); for (int i = 0; i <= maxx; i++) { if (if4 && i == 9) continue; cnt += dfs(len - 1, i == 4, shangxian && i == maxx); //只有之前有限制現在的達到了上限才能構成限制 } return shangxian ? cnt : dp[len][if4] = cnt; //如果有限制,那么就不能記憶化,否則記憶的是個錯誤的數. } int solve(int x) { memset(shu, 0, sizeof(shu)); int k = 0; while (x) { shu[++k] = x % 10; //保存a,b的數 x /= 10; } return dfs(k, false, true); } int main() { scanf("%d%d", &a, &b); printf("%d\n", solve(b) - solve(a - 1)); //while (1); return 0; }
再來看一道題:例題2.求a~b中不包含62和4的數的個數. 0 < a、b < 2*10^9
分析:和上一題一樣,只需要再判斷一下4是否出現和上一位是否為6即可.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int a, b,shu[20],dp[20][2]; int dfs(int len, bool if6, bool shangxian) { if (len == 0) return 1; if (!shangxian && dp[len][if6]) return dp[len][if6]; int cnt = 0, maxx = (shangxian ? shu[len] : 9); for (int i = 0; i <= maxx; i++) { if (i == 4 || if6 && i == 2) continue; cnt += dfs(len - 1, i == 6, shangxian && i == maxx); } return shangxian ? cnt : dp[len][if6] = cnt; } int solve(int x) { memset(shu, 0, sizeof(shu)); int k = 0; while (x) { shu[++k] = x % 10; x /= 10; } return dfs(k, false, true); } int main() { scanf("%d%d", &a, &b); printf("%d\n", solve(b) - solve(a - 1)); return 0; }
例題3:bzoj1026 windy數
對於這道題,我寫了一個較為詳細的題解:傳送門
例題4:找出1~n范圍內含有13並且能被13整除的數字的個數.
分析:和例1相比多了一個整除,怎么處理呢?其實只需要在記憶化搜索中增加一個參數mod即可,利用(a * b) % mod = (a % mod) * (b % mod)和(a + b) % mod = (a % mod) + (b % mod)來計算.比如說73 % 10 = ((7 % 10) * 10 + 3) % 10,但要注意本題是要找含有13的數,那么在處理的時候就要進行分類討論.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n, shu[20], dp[20][20][10]; int dfs(int len, int mod, int zhuangtai, bool shangxian) { if (len == 0) return mod == 0 && zhuangtai == 2; if (!shangxian && dp[len][mod][zhuangtai]) return dp[len][mod][zhuangtai]; int cnt = 0, maxx = (shangxian ? shu[len] : 9); for (int i = 0; i <= maxx; i++) { int tz = zhuangtai; if (zhuangtai != 2 && i != 1) tz = 0; if (zhuangtai == 1 && i == 3) tz = 2; if (i == 1 && zhuangtai != 2) tz = 1; cnt += dfs(len - 1, (mod * 10 + i) % 13, tz, shangxian && i == maxx); } if (!shangxian) dp[len][mod][zhuangtai] = cnt; return cnt; } int main() { while (~scanf("%d", &n)) { memset(shu, 0, sizeof(shu)); memset(dp, 0, sizeof(dp)); int k = 0; while (n) { shu[++k] = n % 10; n /= 10; } printf("%d\n", dfs(k, 0, 0, 1)); } return 0; }
例題5:找出區間內平衡數的個數,所謂的平衡數,就是以這個數字的某一位為支點,另外兩邊的數字大小乘以力矩之和相等,即為平衡數。
分析:對於這道題,如果一個數中每個數位到支點的距離*這個數位的和為0,那么這個數為平衡數.這樣我們定義狀態就要考慮力矩和和支點.支點可以在dfs前枚舉得到,力矩和可以在處理每個數位的時候得到.但是這個算法是有缺陷的,例如0000,000000也會被統計,我們只需要減去給定范圍0全是0的數的個數即可.這里可以進行一個小小的優化,如果力矩和已經為負數,說明已經處理到了支點左邊,接着處理下去絕對會小於0,那么回溯即可.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int t; long long dp[19][19][2005]; long long l, r; int shu[20]; long long dfs(int len, int zhidian, int liju, bool shangxian) { if (len == 0) return liju == 0; if (liju < 0) return 0; if (!shangxian && dp[len][zhidian][liju]) return dp[len][zhidian][liju]; long long cnt = 0; int maxx = (shangxian ? shu[len] : 9); for (int i = 0; i <= maxx; i++) { int temp = liju; temp += (len - zhidian) * i; cnt += dfs(len - 1, zhidian, temp, shangxian && i == maxx); } if (!shangxian) dp[len][zhidian][liju] = cnt; return cnt; } long long solve(long long x) { int k = 0; while (x) { shu[++k] = x % 10; x /= 10; } long long ans = 0; for (int i = 1; i <= k; i++) ans += dfs(k, i, 0, 1); return ans - (k - 1); } int main() { scanf("%d", &t); memset(dp, 0, sizeof(dp)); while (t--) { scanf("%lld%lld", &l, &r); printf("%lld\n", solve(r) - solve(l - 1)); } //while (1); return 0; }
例題6:
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 14628 | Accepted: 5878 |
Description
The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro, Sham, Bo', and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can't even flip a coin because it's so hard to toss using hooves.
They have thus resorted to "round number" matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both "round numbers", the first cow wins,
otherwise the second cow wins.
A positive integer N is said to be a "round number" if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.
Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many "round numbers" are in a given range.
Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).
Input
Output
Sample Input
2 12
Sample Output
6
Source
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; ll a, b, num[40], cnt, f[100][100][100]; ll dfs(ll len, ll num1, ll num0, bool limit,bool can) { if (len == 0) { if (num0 >= num1) return 1; return 0; } if (!limit && f[len][num1][num0]) return f[len][num1][num0]; ll cntt = 0, shangxian = (limit ? num[len] : 1); for (int i = 0; i <= shangxian; i++) { if (i == 0) { if (can) cntt += dfs(len - 1, num1, num0 + 1, (limit && i == shangxian), can || i == 1); else cntt += dfs(len - 1, num1, num0, (limit && i == shangxian), can || i == 1); } if (i == 1) cntt += dfs(len - 1, num1 + 1, num0, (limit && i == shangxian),can || i == 1); } if (!limit) return f[len][num1][num0] = cntt; else return cntt; } ll solve(ll x) { memset(num, 0, sizeof(num)); cnt = 0; while (x) { num[++cnt] = x % 2; x /= 2; } return dfs(cnt, 0, 0, true,false); } int main() { scanf("%lld%lld", &a, &b); printf("%lld\n", solve(b) - solve(a - 1)); return 0; }
例題7(一道noip模擬賽題):
分析:顯然是一道數位dp題,不過需要一些奇怪的姿勢.常規的數位dp能統計出一個區間內滿足條件的數的個數,可是我們要求第k個,怎么辦呢?轉化為經典的二分問題,我們二分當前數的大小,看它是第幾大的,就可以了.
顯然數位dp套上模板,再用上kmp的next數組就可以了,傳遞4個參數:還剩下多少位沒有匹配,匹配了多少位,是否達到上限和是否匹配成功,到最后判斷一下就可以了.
學到了一種很強的思想:如果能求出i是第幾個數,要求出第k個數就可以二分i的值.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; long long L, R, K, f[20][2010][2]; int m, nextt[40], a[30]; char X[100]; void init() { nextt[0] = 0; nextt[1] = 0; for (int i = 1; i < m; i++) { int j = nextt[i]; while (j && X[i] != X[j]) j = nextt[j]; nextt[i + 1] = X[i] == X[j] ? j + 1 : 0; } } long long dfs(int len, int w, bool limit, bool flag) { if (len == 0) return flag; if (!limit && f[len][w][flag] != -1) return f[len][w][flag]; int maxn = limit ? a[len] : 9; long long cnt = 0; for (int i = 0; i <= maxn; i++) { int t = w; while (t && X[t] - '0' != i) t = nextt[t]; if (X[t] - '0' == i) t++; cnt += dfs(len - 1, t, limit && (i == a[len]), flag || (t == m)); } return limit ? cnt : f[len][w][flag] = cnt; } long long query(long long u) { int cnt = 0; while (u) { a[++cnt] = u % 10; u /= 10; } memset(f, -1, sizeof(f)); return dfs(cnt, 0, 1, 0); } int main() { scanf("%lld %lld %s %lld", &L, &R, X, &K); m = strlen(X); init(); if (query(R) < K + query(L - 1)) { printf("Hey,wake up!\n"); return 0; } long long t = query(L - 1),ans = L; long long l = L, r = R; while (l < r) { long long mid = (l + r) >> 1; if (query(mid) - t >= K) { r = mid; ans = mid; } else l = mid + 1; } printf("%lld\n", r); return 0; }
例題8:
XHXJ's LIS
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3649 Accepted Submission(s): 1521
If you do not know xhxj, then carefully reading the entire description is very important.
As the strongest fighting force in UESTC, xhxj grew up in Jintang, a border town of Chengdu.
Like many god cattles, xhxj has a legendary life:
2010.04, had not yet begun to learn the algorithm, xhxj won the second prize in the university contest. And in this fall, xhxj got one gold medal and one silver medal of regional contest. In the next year's summer, xhxj was invited to Beijing to attend the astar onsite. A few months later, xhxj got two gold medals and was also qualified for world's final. However, xhxj was defeated by zhymaoiing in the competition that determined who would go to the world's final(there is only one team for every university to send to the world's final) .Now, xhxj is much more stronger than ever,and she will go to the dreaming country to compete in TCO final.
As you see, xhxj always keeps a short hair(reasons unknown), so she looks like a boy( I will not tell you she is actually a lovely girl), wearing yellow T-shirt. When she is not talking, her round face feels very lovely, attracting others to touch her face gently。Unlike God Luo's, another UESTC god cattle who has cool and noble charm, xhxj is quite approachable, lively, clever. On the other hand,xhxj is very sensitive to the beautiful properties, "this problem has a very good properties",she always said that after ACing a very hard problem. She often helps in finding solutions, even though she is not good at the problems of that type.
Xhxj loves many games such as,Dota, ocg, mahjong, Starcraft 2, Diablo 3.etc,if you can beat her in any game above, you will get her admire and become a god cattle. She is very concerned with her younger schoolfellows, if she saw someone on a DOTA platform, she would say: "Why do not you go to improve your programming skill". When she receives sincere compliments from others, she would say modestly: "Please don’t flatter at me.(Please don't black)."As she will graduate after no more than one year, xhxj also wants to fall in love. However, the man in her dreams has not yet appeared, so she now prefers girls.
Another hobby of xhxj is yy(speculation) some magical problems to discover the special properties. For example, when she see a number, she would think whether the digits of a number are strictly increasing. If you consider the number as a string and can get a longest strictly increasing subsequence the length of which is equal to k, the power of this number is k.. It is very simple to determine a single number’s power, but is it also easy to solve this problem with the numbers within an interval? xhxj has a little tired,she want a god cattle to help her solve this problem,the problem is: Determine how many numbers have the power value k in [L,R] in O(1)time.
For the first one to solve this problem,xhxj will upgrade 20 favorability rate。
0<L<=R<2 63-1 and 1<=K<=10).
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; int T,cas; ll L,R,K,f[20][(1 << 10) + 1][11],num[20]; ll calc(ll S,ll &k,ll x) { int cur = -1; for (ll i = x; i <= 9; i++) if (S & (1 << i)) { cur = i; break; } if (cur == -1) { k++; S |= (1 << x); } else { S ^= (1 << cur); S |= (1 << x); } return S; } ll dfs(ll len,ll S,ll k,bool limit,bool flag) { if (!len) return k == K; if (!limit && f[len][S][K] != -1) return f[len][S][K]; ll maxx = (limit ? num[len] : 9),cnt = 0; for (ll i = 0; i <= maxx; i++) { ll tempk = k,tempS; if (!i && flag) tempS = 0; else tempS = calc(S,tempk,i); cnt += dfs(len - 1,tempS,tempk,i == maxx && limit,flag && !i); } if (!limit) return f[len][S][K] = cnt; return cnt; } ll solve(ll x) { ll len = 0; while (x) { num[++len] = x % 10; x /= 10; } return dfs(len,0,0,1,1); } int main() { memset(f,-1,sizeof(f)); scanf("%d",&T); while (T--) { scanf("%lld%lld%lld",&L,&R,&K); printf("Case #%d: %lld\n",++cas,solve(R) - solve(L - 1)); } return 0; }
經過對以上例題的探討與研究,自然也就不難得到數位dp模板(...根據實際情況來填):
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int t; long long dp[19][19][2005]; long long l, r; int shu[20]; long long dfs(int len,..., bool shangxian) { if (len == 0) return ...; if (!shangxian && dp[len][...]) return dp[len][...]; //dp數組的內容應和dfs調用參數的內容相同,除了是否達到上限 long long cnt = 0; int maxx = (shangxian ? shu[len] : 9); for (int i = 0; i <= maxx; i++) { ...; cnt += dfs(len - 1,..., shangxian && i == maxx); } if (!shangxian) dp[len][...] = cnt; return cnt; } long long solve(long long x) { int k = 0; while (x) { shu[++k] = x % 10; x /= 10; } return dfs(k,...,1) } int main() { memset(dp, 0, sizeof(dp)); scanf("%lld%lld", &l, &r); //有些題目其實並不需要用到long long printf("%lld\n", solve(r) - solve(l - 1)); //只有滿足區間減法才能用 //while (1); return 0; }
總結:
1.如果題目中出現求滿足區間[l,r]的符合......性質的數的個數,考慮使用數位dp.
2.思考一下:如果我們只能從前往后一位位枚舉當前的數位,要做出這道題,我們需要知道哪些量?利用這些來補充到dfs的調用參數中.
3.套用模板.