類型:數位DP
傳送門:>Here<
題意:問區間$[n,m]$的數字中,不含4以及62的數字總數
解題思路
數位DP入門題
先考慮一般的暴力做法,整個區間掃一遍,判斷每個數是否合法並累計答案。而數位DP則認為可以換一種方法來枚舉,找到對於一個數的上限,然后在這個限度內枚舉每一個數位來統計答案
為了方便數位DP,題意可以轉化求區間$[0, k]$的符合要求的數字總數,因此答案就是$ans(M)-ans(N-1)$
首先我們可以預處理出dp數組:$dp[i][j]$表示以$j$開頭的$i$位數的符合要求的數字總數;例如,$dp[2][3]$表示以3開頭的2位數中符合要求的,也就是區間$[30, 39]$中符合要求的。$$dp[i][j] = \sum\limits_{0 \leq k \leq 9 \\ \ k \neq 4}dp[i-1][k]$$這個方程很好理解,相當於枚舉一個數位塞到前面,同時需要保證不能把6塞當2前面,並且特判一下4就好了
至於統計答案,我們從上限的最高位開始往下掃描。這里有個很巧妙的思想——每次處理不超過當前這一位的部分。形象地說,對於數字$21358$,最高位掃描$0~1$,也就是把答案累積上$dp[5][0~1]$。這一步相當於處理了區間$[0, 19999]$中的所有;此時默認最高位是2,掃描到下一位,累積$dp[4][0~0]$,也就相當於處理了區間$[20000,20999]$;依次類推,然后將會處理$[21000,21299]$,$[21300,21349]$,$[21350,21357]$。因此我們可以在$O(lg N * 10)$的復雜度內處理區間$[0, N-1]$。(注意不包括N)$$ans = \sum\limits_{i=num}^{1}\sum\limits_{j=0}^{digit[i]-1}dp[i][j]$$
然后在來看判斷62和4的問題:每當我們進入到下一位,我們就將默認上一位確定。此時若確定的那一位為4,那么之后的都不用考慮了(一定不合法)。同理,如果當前確定的為2且上一位確定的為6,那么也可以跳出。事實上,這個跳出不是優化,而是必須那么做——如果不跳出,就會錯誤地累積很多答案。同時,不僅進入下一位的時候要判斷,掃描的時候也要判斷。道理一樣
拓展:如果題目要求的不是【不要62】而是【要62】呢?就好像[HDU3555] Bomb所要求的一樣,只需要求出所有的【不要62】數字,用N減一下就好了
Code
特別需要注意的是$digit[num+1]==0$這一步的處理,如果不加這一步,那么如果在處理前一個數字時殘留下了$digit[num+1]==6$,那么你的程序將不能在最高位填充2了!
另外還有dp數組的初始化問題:一種是$dp[0][0]=1$,或者對於所有$i \neq 4$,$dp[1][i]=1$。其實這兩者是等效的,因為在統計$dp[1][i]$時,只會累積到$dp[0][0]$為1
/*By DennyQi 2018.8.13*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 27010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w; } int N,M,num,X,Y; int dp[10][11],digit[10]; inline void Init(){ dp[0][0] = 1; for(int i = 1; i <= 7; ++i){ for(int j = 0; j <= 9; ++j){ if(j == 4) continue; for(int k = 0; k <= 9; ++k){ if(k == 2 && j == 6) continue; dp[i][j] += dp[i-1][k]; } } } } inline int cul(int x){ num = 0; int y = x, res = 0; while(y > 0){ digit[++num] = y % 10; y /= 10; } digit[num+1] = -1; for(int i = num; i >= 1; --i){ for(int j = 0; j < digit[i]; ++j){ if(digit[i+1] == 6 && j == 2) continue; res += dp[i][j]; } if(digit[i] == 4 || (digit[i+1]==6&&digit[i]==2)) break; } return res; } int main(){ Init(); for(;;){ N = r, M = r; if(!N && !M) break; printf("%d\n",cul(M+1)-cul(N)); } return 0; }