1. 程式人生 > >【題目整理】數位dp(入門)

【題目整理】數位dp(入門)

先丟一個模板

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));
    }
}