【題目整理】數位dp(入門)
阿新 • • 發佈:2018-12-06
先丟一個模板
typedef long long ll; int a[20]; ll dp[20][state];//不同題目狀態不同 ll dfs(int pos,/*state變數*/,bool lead/*前導零*/,bool limit/*數位上界變數*/)//不是每個題都要判斷前導零 { //遞迴邊界,既然是按位列舉,最低位是0,那麼pos==-1說明這個數我列舉完了 if(pos==-1) return 1;/*這裡一般返回1,表示你列舉的這個數是合法的,那麼這裡就需要你在列舉時必須每一位都要滿足題目條件,也就是說當前列舉到pos位,一定要保證前面已經列舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */ //第二個就是記憶化(在此前可能不同題目還能有一些剪枝) if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state]; /*常規寫法都是在沒有限制的條件記憶化,這裡與下面記錄狀態是對應,具體為什麼是有條件的記憶化後面會講*/ int up=limit?a[pos]:9;//根據limit判斷列舉的上界up;這個的例子前面用213講過了 ll ans=0; //開始計數 for(int i=0;i<=up;i++)//列舉,然後把不同情況的個數加到ans就可以了 { if() ... else if()... ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變數傳參都是這樣寫的 /*這裡還算比較靈活,不過做幾個題就覺得這裡也是套路了 大概就是說,我當前數位列舉的數是i,然後根據題目的約束條件分類討論 去計算不同情況下的個數,還有要根據state變數來保證i的合法性,比如題目 要求數位上不能有62連續出現,那麼就是state就是要儲存前一位pre,然後分類, 前一位如果是6那麼這意味就不能是2,這裡一定要儲存列舉的這個數是合法*/ } //計算完,記錄狀態 if(!limit && !lead) dp[pos][state]=ans; /*這裡對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裡就是lead就完全不用考慮了*/ return ans; } ll solve(ll x) { int pos=0; while(x)//把數位都分解出來 { a[pos++]=x%10;//個人老是喜歡編號為[0,pos),看不慣的就按自己習慣來,反正注意數位邊界就行 x/=10; } return dfs(pos-1/*從最高位開始列舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛 } int main() { ll le,ri; while(~scanf("%lld%lld",&le,&ri)) { //初始化dp陣列為-1,這裡還有更加優美的優化,後面講 printf("%lld\n",solve(ri)-solve(le-1)); } }
hdu2089 不要62
【題意】給定一個區間,求在這個區間內的不含4和連續62的數。
【解題思路】設dp[i][j]為前一位的狀態為j(狀態即第i位是否是6)的符合要求的數的個數,然後就是套模板。
【程式碼】
#include<bits/stdc++.h> using namespace std; int n,m; int a[105],dp[105][2]; int dfs(int pos,int state,int limit) //state:當前一位是否是6,limit:當前一位是否存在數字限制 { if(pos==-1)return 1;//遞迴邊界 if(!limit && dp[pos][state]!=-1) return dp[pos][state];//如果當前狀態已經被記錄了可以直接返回 int up=limit?a[pos]:9; int ans=0; for(int i=0;i<=up;i++) { if(i==4 || (state && i==2))continue;//去掉這一位是4或者連續62的情況 ans+=dfs(pos-1,i==6,limit && i==a[pos]); } if(!limit)dp[pos][state]=ans;//當前一位置上數字沒有限制時,可以記憶化 return ans; } int solve(int x) { int cnt=0; while(x) { a[cnt++]=x%10; x/=10; } dfs(cnt-1,0,1); } int main() { while(~scanf("%d%d",&n,&m) && n ||m) { memset(dp,-1,sizeof(dp)); for(int i=0;i<2;i++) for(int j=0;j<2;j++) printf("i=%d , %d\n",i,dp[i][j]); printf("%d\n",solve(m)-solve(n-1)); } }