題目鏈接: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;
}