數位dp如何入門
我最初在網上漫遊了一天,終於找到了一個適合自己的學習方式,這裡分享給大家。搜數位dp大部分都是記憶化搜尋寫的,因為這個搜尋是反向的,所以一看是看上去真的什麼都看不懂,因此我們首先學一下遞推版的,這裡給出網址,大家可以去看一下寫的真的很好,認真看就能看明白初探數位dp,然後看懂了之後就手寫代碼,通常寫hdu2089(不要62)這個入門題,這樣我們就可以開始看記憶化搜尋了,只是反向而已,那麼既然有正推為什麼還要學記憶化,首先學會之後比較好學,然後就是記憶化其實真的不難,學會之後很好處理。
給出記憶化的模板:
摘自Beginner
// pos = 當前處理的位置(一般從高位到低位)// pre = 上一個位的數字(更高的那一位)// status = 要達到的狀態,如果為1則可以認為找到了答案,到時候用來返回,// 給計數器+1。// limit = 是否受限,也即當前處理這位能否隨便取值。如567,當前處理6這位,// 如果前面取的是4,則當前這位可以取0-9。如果前面取的5,那麼當前// 這位就不能隨便取,不然會超出這個數的範圍,所以如果前面取5的// 話此時的limit=1,也就是說當前只可以取0-6。//// 用DP數組儲存這三個狀態是因為往後轉移的時候會遇到很多重複的情況。int dfs(int pos,int pre,int status,int limit){ //已結搜到盡頭,返回"是否找到了答案"這個狀態。 if(pos < 1) return status; //DP裡儲存的是完整的,也即不受限的答案,所以如果滿足的話,可以直接返回。 if(!limit && DP[pos][pre][status] != -1) return DP[pos][pre][status]; int end = limit ? DIG[pos] : 9; int ret = 0; //往下搜的狀態表示的很巧妙,status用||是因為如果前面找到了答案那麼後面 //還有沒有答案都無所謂了。而limti用&&是因為只有前面受限、當前受限才能 //推出下一步也受限,比如567,如果是46X的情況,雖然6已經到盡頭,但是後面的 //個位仍然可以隨便取,因為百位沒受限,所以如果個位要受限,那麼前面必須是56。 // //這裡用"不要49"一題來做例子。 for(int i = 0;i <= end;i ++) ret += dfs(pos - 1,i,status || (pre == 4 && i == 9),limit && (i == end)); //DP裡儲存完整的、取到盡頭的資料 if(!limit) DP[pos][pre][status] = ret; return ret;}
這裡是我自己寫的幾道練手題: hdu 2089 不要62
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int maxn = 35;LL dp[maxn][10][2],bit[maxn];LL dfs(int pos,int pre,int is,int f){ if(pos < 1) return is; if(!f && dp[pos][pre][is] != -1) return dp[pos][pre][is]; int last = f?bit[pos]:9; LL res = 0; for(int i = 0; i <= last; i++){ res += dfs(pos-1, i, is||(pre == 4 && i == 9), f&&i==last); } if(!f) dp[pos][pre][is] = res; return res;}LL solve(LL n){ int len = 0; while(n){ bit[++len] = n%10; n /= 10; } return dfs(len, 0, 0, 1); //當前的位置,前一個數是多少,該書中是否已經含有49,f我不知道什麼意思}int main(){ int T; scanf("%d", &T); while(T--){ LL n; scanf("I64d", &n); printf("%I64d\n", solve(n)); } return ;}hdu 3555 bomb
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int maxn = 35;LL dp[maxn][10][2],digit[maxn];LL dfs(int pos,int pre,int is,int limit){ //limit表示是否有限制 if(pos < 1) return is; //是否有49 if(!limit && dp[pos][pre][is] != -1) return dp[pos][pre][is]; int last = limit ? digit[pos]:9; //f用來判斷是否能隨意取 LL ret = 0; for(int i = 0; i <= last; i++) ret += dfs(pos-1, i, is||(pre==4 && i==9), limit&&i==last); if(!limit) dp[pos][pre][is] = ret; return ret;}LL solve(LL n){ int len = 0; memset(digit, 0, sizeof(digit)); while(n){ digit[++len] = n%10; n /= 10; } return dfs(len, 0, 0, 1);}int main(){ int T; scanf("%d", &T); memset(dp, -1, sizeof(dp)); while(T--){ LL n; scanf("%I64d", &n); printf("%I64d\n", solve(n)); } return 0;}hdu 4722 Good Numbers
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int MOD = 10;const int maxn = 25;LL dp[maxn][15],dig[maxn];LL dfs(int pos, int mod, int limit){ if(pos < 1) return mod == 0; if(!limit && dp[pos][mod] != -1) return dp[pos][mod]; int len = limit?dig[pos]:9; LL ret = 0; for(int i = 0; i <= len; i++) ret += dfs(pos-1, (mod+i)%MOD, limit&&i==len); if(!limit) dp[pos][mod] = ret; return ret;}LL solve(LL n){ LL len = 0; while(n){ dig[++len] = n%10; n /= 10; } return dfs(len, 0, 1);}int main(){ int T; scanf("%d", &T); LL A,B; memset(dp, -1, sizeof(dp)); for(int kase = 1; kase <= T; kase++){ scanf("%I64d%I64d", &A, &B); printf("Case #%d: ", kase); if(A == 0) printf("%I64d\n", solve(B)); //注意A取0,那麼A-1小於0,不合理,因此需要特判 else printf("%I64d\n", solve(B) - solve(A-1)); } return 0;}