HDU - 2089 不要62(數位dp,通過這個最基礎的題來隨便談談數位dp)

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=2089

題目意思:給定區間沒有62和4的數有多少個

思路:

數位dp包括數位和dp。數位就是通過把數分解成一位一位的數來分析。那麼數位dp的dp用來記憶什麼東西呢?

在一個區間中(比如【1,100000】),找沒有62和4的數,一個一個檢算很顯然會有很多重複的部分

(比如【101,110】和【1001,1010】這兩個區間中滿足條件的個數都是9,而且抽象來講這兩個區間很相似,仔細想想哪裏相似)。那麼就可以很快求出這個區間中你想要的數的個數。

現在來講講哪裏相似(就這道題來說),這兩個區間末尾都是1到10,那麼對於最後一位數(個位)他們前面一位數(十位)都是相同的。所以,如果都屬於同一位數(比如最後一位),且前一位數都相同,那麼就可以直接用,這就是dp的兩維了。

現在還有最後一個問題,就是什麼情況下可以用這個dp(太懶了,從大佬博客扣了下面這段):

       ********

控制上界枚舉,從最高位開始往下枚舉,例如:ri=213,那麼我們從百位開始枚舉:百位可能的情況有0,1,2(覺得這裏枚舉0有問題的繼續看)

然後每一位枚舉都不能讓枚舉的這個數超過上界213(下界就是0或者1,這個次要),當百位枚舉了1,那麼十位枚舉就是從0到9,因爲百位1已經比上界2小了,後面數位枚舉什麼都不可能超過上界。所以問題就在於:當高位枚舉剛好達到上界是,那麼緊接着的一位枚舉就有上界限制了。具體的這裏如果百位枚舉了2,那麼十位的枚舉情況就是0到1,如果前兩位枚舉了21,最後一位之是0到3(這一點正好對於代碼模板裏的一個變量limit 專門用來判斷枚舉範圍)。

相信讀者還對這個有不少疑問,筆者認爲有必要講一下記憶化爲什麼是if(!limit)才行,大致就是說有無limit會出現狀態衝突,舉例:

約束:數位上不能出現連續的兩個1(11、112、211都是不合法的)

假設就是[1,210]這個區間的個數

狀態:dp[pos][pre]:當前枚舉到pos位,前面一位枚舉的是pre(更加前面的位已經合法了),的個數(我的pos從0開始)

先看錯誤的方法計數,就是不判limit就是直接記憶化

那麼假設我們第一次枚舉了百位是0,顯然後面的枚舉limit=false,也就是數位上0到9的枚舉,然後當我十位枚舉了1,此時考慮dp[0][1],就是枚舉到個位,前一位是1的個數,顯然dp[0][1]=9;(個位只有是1的時候是不滿足的),這個狀態記錄下來,繼續dfs,一直到百位枚舉了2,十位枚舉了1,顯然此時遞歸到了pos=0,pre=1的層,而dp[0][1]的狀態已經有了即dp[pos][pre]!=-1;此時程序直接return dp[0][1]了,然而顯然是錯的,因爲此時是有limit的個位只能枚舉0,根本沒有9個數,這就是狀態衝突了。有lead的時候可能出現衝突,這只是兩個最基本的不同的題目可能還要加限制,反正宗旨都是讓dp狀態唯一

*******

所以,只有當此時狀態沒有限制(limit)時才能使用dp來記憶循環使用。

 

好了,下面貼一下這個題的代碼:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define eps 1e-8
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;

int l,r;
int a[20];
int dp[20][20];
int dfs(int pos,int sta,bool limit){
    if (pos==-1) return 1;
    if (!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit?a[pos]:9;
    int tmp=0;
    for (int i=0;i<=up;i++){
        if (i==4) continue;
        if (sta==6&&i==2) continue;
        tmp+=dfs(pos-1,i,a[pos]==i&&limit);
    }
    if (!limit) dp[pos][sta]=tmp;
    return tmp;
}
int solve(int x){
    int pos=0;
    while (x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1);
}
int main() {
    while (~scanf ("%d%d",&l,&r)&&l&&r){
        memset(dp,-1,sizeof(dp));
        printf ("%d\n",solve(r)-solve(l-1));
    }
    return 0;
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章