【ybtoj】【數位dp專題】
阿新 • • 發佈:2021-08-31
前言
假期最後兩天不想做什麼太難的,就把數位DP開了吧!正好填之前挖的坑
數位DP看起來貌似都比較裸...而且題目簡短,注意一下程式碼的細節就好
本篇記錄裡全部使用記憶化搜尋
目錄
-
A. 【例題1】B數計數
-
B. 【例題2】區間圓數
-
C. 【例題3】數字計數
-
D. 【例題4】數字整除
-
F. 1.幸運數字
題解
A. 【例題1】B數計數
分析:
此題是第一道ybt的數位dp題,引入一些模板的寫法
對於所有求1~n,L~R的 xx數 個數的,一般都是從高位到低位搜尋,而且記錄一個 ok 變數來表示當前數位可不可以隨便填數字(每一位數字填完最後不能比 n 大)
由於ok 變數不需要在 dp 數組裡記錄,可以直接傳參到記憶化搜尋裡,但是ok==0和ok==1時候 dp 陣列的記憶化值是不一樣的,所以規定只記憶化ok==1(即可以隨便填)時的 dp 值,這是因為ok==0的情況很少,所以不用記憶化問題也不大(ps:測試時發現列舉 i 的時候倒序列舉也可以使記憶化不重複,但是過於玄學我就不提了)
實際上,應該也可以在 dp 陣列中記錄 ok ,理論上空間會變大一倍(ok取值0,1),但是搜尋部分會快一點點
那麼迴歸本題,設計dp狀態為 dp[pos][res][op]
pos表示第幾位,res表示餘數,op表示13數出現的狀態(op==0沒出現過,op==1上一位是1而且之前沒出現過完整的13,op==2表示出現過13),maxn表示當前最大能填的數字,ok表示當前這位是否可以隨便填
(一開始我想用check表示是否出現過13,其實不需要,可以用op代替)
程式碼:
A. 【例題1】B數計數
#include<bits/stdc++.h> using namespace std; #define ll long long const int INF = 0x3f3f3f3f; int n,dp[11][14][3],dig[11],m,mi[11]; void init() { //memset(dp,-1,sizeof(dp)); //memset(dig,0,sizeof(dig)); m=0; while(n) { int x=n%10; dig[++m]=x; n/=10; } } //可以有前導零,不用記錄zero //pos表示第幾位,res表示餘數,op表示13數出現的狀態,maxn表示當前最大能填的數字 //(一開始我想用check表示是否出現過13,其實不需要,可以用op代替) //ok表示當前這位是否可以隨便填 int solve(int pos,int res,int op,bool ok) { if(pos==0) return dp[pos][res][op]=(op==2&&res==0); if(dp[pos][res][op]!=-1&&ok) return dp[pos][res][op]; int ret=0,maxn=9; if(!ok) maxn=dig[pos]; for(int i=maxn;i>=0;i--) { int tmp=(res+mi[pos]*i)%13; //分類討論當前填的數字大小 if(i<maxn) { //if(check) ret+=solve(pos-1,tmp,op,1,1); if(op==2) {ret+=solve(pos-1,tmp,2,1);continue;} if(i==1) ret+=solve(pos-1,tmp,1,1); else if(i==3&&op==1) ret+=solve(pos-1,tmp,2,1); else ret+=solve(pos-1,tmp,0,1); } else { if(op==2) {ret+=solve(pos-1,tmp,2,ok);continue;} if(i==1) ret+=solve(pos-1,tmp,1,ok); else if(i==3&&op==1) ret+=solve(pos-1,tmp,2,ok); else ret+=solve(pos-1,tmp,0,ok); } } // printf("dp[%d][%d][%d]=%d\n",pos,res,op,ret); if(ok) dp[pos][res][op]=ret; return ret; } int main() { mi[1]=1; for(int i=2;i<=10;i++) mi[i]=mi[i-1]*10; while(scanf("%d",&n)!=EOF) { init(); printf("%d\n",solve(m,0,0,0)); } return 0; } /* input: 131312 131313 13333 13332 13338 output: 550 551 60 60 61 */