數位dp,是一種用來計數的dpphp
若是如今給你一道題,須要你求在區間[l,r]內知足條件的解的個數,咱們很容易想到去暴力枚舉,但要是數據範圍太大這種辦法就行不通了,這時候數位dp就派上了用場,所謂數位就是把一個數拆成一個一個進制位,而後逐一比較看是否知足題目要求,這其實也是一種暴力方法,只不過期間複雜度小了不少c++
那麼到底要如何作呢?下面咱們來看一道例題git
HDU2089ide
歸納一下題目意思spa
就是給你一個區間[n,m],要你求區間內不含"62"或"4"的數字的個數,如8134(含4),21262455(含62)均不知足題意,而61342這種"6"和"2"並不連在一塊兒的數字則知足題意code
直接統計對於暴力枚舉很好求,可是對於數位dp並不容易,因此咱們還須要用到差分的思想,即統計0到b+1(注意不是b,至於爲何後面會講)和0到a的知足條件的個數,再二者相減blog
進一步化簡,求0到i位數不含4和62的個數get
i=1,求0~9的知足條件的個數it
i=2,求0~99的知足條件的個數event
i=3,求0~999的知足條件的個數
i=4,求0~9999的知足條件的個數
...
用dp[i][0]表示i位數中幸運數的個數
用dp[i][1]表示i位數中以2開頭的幸運數的個數
用dp[i][2]表示i位數中非幸運數的個數
那麼,就有如下的遞推公式
dp[i][0]=dp[i-1][0]*9-dp[i-1][1]//表示前i-1位數字中的幸運數前面加上除4之外的0~9的其餘數字,共9個,還要減去前i-1位數字中的以2開頭的幸運數加上這一位以6開頭的數字的個數
dp[i][1]=dp[i-1][0]//表示前i-1位數字中的幸運數加上這一位的2
dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0]//表示前面已經不合法的數字這一位不管放什麼都不合法,因此0~9隨便放,加上前i-1位數字中的以2開頭的幸運數加上這一位的6,再加上前i-1位數字中的幸運數加上這一位的4的個數
初始值 dp[0][0]=1,其餘均爲0
根據初始值和遞推公式,咱們就能獲得從0到任意i位數字的吉利數字的個數。
找到0 ~ n 的吉利數字的個數
咱們先求出0 ~ n 之間非吉利數字的個數,用總數減去便可。那,非吉利數字的個數怎麼求呢?
用具體的數字舉例來講吧:設 n = 583626
用digit[10]記錄n+1每一位對應的數字,此例中有6位數字(令cnt = 6 表示數字位數),分別是
digit[6] = 5
digit[5] = 8
digit[4] = 3
digit[3] = 6
digit[2] = 2
digit[1] = 7
digit[0] = 任意數字,佔位用的
用sum記錄非吉利數字的個數,初始化爲0
須要一個bool量 flag,記錄是否出現了非吉利數字。初始化爲false, 未出現。
咱們從數字的最高位起進行判斷:digit[6] = 5, 咱們求 0 ~ 499999 之間非吉利數的個數。
首先:加上0 ~ 99999中全部非吉利數字前面添加0~4的任意一個數字的狀況 sum += dp[5][2] * digit[6]
其次:5大於4,故咱們要加上 0~99999中全部吉利數字前面添加4的狀況 sum += dp[5][0]
接着,判斷第5位digit[5] = 8,即判斷500000 ~ 579999 之間的非吉利數字的個數,其實就是判斷0 ~ 79999之間的,前面的數字不是6就沒有什麼用
首先:加上0 ~ 9999中全部非吉利數字前面添加0~7的任意一個數字的狀況 sum += dp[4][2] * digit[5]
其次:8大於4,故咱們要加上 0~9999中全部吉利數字前面添加4的狀況 sum += dp[4][0]
此外:8大於6,故咱們要加上0~9999中全部以2開頭的吉利數字前添加6的狀況 sum += dp[4][1]
接着,判斷第4位digit[4] = 3,即判斷580000 ~ 582999 之間的非吉利數字的個數,其實就是判斷0 ~ 2999之間的
首先:加上0 ~ 999中全部非吉利數字前面添加0~2的任意一個數字的狀況 sum += dp[3][2] * digit[4]
其次:2小於4,沒有須要特別考慮的
此外:2小於6,沒有須要特別考慮的
接着,判斷第3位digit[3] = 6,即判斷583000 ~ 583599 之間的非吉利數字的個數,其實就是判斷0 ~ 599之間的
首先:加上0 ~ 99中全部非吉利數字前面添加0~5的任意一個數字的狀況 sum += dp[2][2] * digit[3]
其次:6大於4,故咱們要加上 0~99中全部吉利數字前面添加4的狀況 sum += dp[2][0]
接着,判斷第2位digit[2] = 2,即判斷583600 ~ 583619 之間的非吉利數字的個數,其實就是判斷0 ~ 19之間的,
首先:加上0 ~ 9中全部非吉利數字前面添加0~1的任意一個數字的狀況 sum += dp[1][2] * digit[2]
其次:2小於4,沒有須要特別考慮的
此外:2小於6,沒有須要特別考慮的
可是,須要注意的是,這裏判斷的數字出現了62,咱們要把flag標識爲true。
最後,判斷第1位digit[1] = 7, 判斷583620 ~ 583626可是這裏flag爲true了,表示前面的數字裏面已經包含了非吉利數字,因此後面須要把全部的數字狀況都加入到非吉利裏面。(正是由於每次判斷的數字末尾都比該位的數字少1,因此最開始要記錄n + 1 的值)
sum += digit[1] * dp[0][2] + digit[1] * dp[0][0]
1 #include<bits/stdc++.h> 2 #define in(i) (i=read()) 3 using namespace std; 4 int read() { 5 int ans=0,f=1; char i=getchar(); 6 while(i<'0'||i>'9') {if(i=='-') f=-1; i=getchar();} 7 while(i>='0'&&i<='9') {ans=(ans<<3)+(ans<<1)+i-'0'; i=getchar();} 8 return ans*f; 9 } 10 int dp[10][3],digit[15]; 11 void init() { 12 memset(dp,0,sizeof(dp)); 13 dp[0][0]=1; 14 for(int i=1;i<=8;i++) { 15 dp[i][0]=dp[i-1][0]*9-dp[i-1][1]; 16 dp[i][1]=dp[i-1][0]; 17 dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0]; 18 } 19 } 20 int solve(int x) 21 { 22 memset(digit,0,sizeof(digit)); 23 int cnt=0,tmp=x; 24 while(tmp) { 25 digit[++cnt]=tmp%10; 26 tmp/=10; 27 } 28 digit[cnt+1]=0; int flag=0,ans=0; 29 for(int i=cnt;i>=1;i--) { 30 ans+=digit[i]*dp[i-1][2]; 31 if(flag) ans+=digit[i]*dp[i-1][0]; 32 else { 33 if(digit[i]>4) ans+=dp[i-1][0]; 34 if(digit[i]>6) ans+=dp[i-1][1]; 35 if(digit[i+1]==6 && digit[i]>2) ans+=dp[i][1]; 36 } 37 if(digit[i]==4 || (digit[i+1]==6 && digit[i]==2)) flag=1; 38 } 39 return x-ans; 40 } 41 int main() 42 { 43 int a,b; init(); 44 while(1) { 45 in(a);in(b); 46 if(!a && !b) break; 47 cout<<solve(b+1)-solve(a)<<endl; 48 } 49 return 0; 50 }
最後說那個b+1的狀況,咱們看到代碼中有判斷digit[i]>4和digit[i]>6等相似的語句,咱們處理第i位時,其實是處理0~digit[i]-1,即[ 0,digit[i] ),而把digit[i]放到下一次去判斷,但咱們處理個位時,最後一個是不會去統計的,因此咱們把統計的範圍+1,即爲[ 0,digit[i]+1 )-->[ 0,digit[i] ],因此就有了solve(b+1)-solve(a)這樣的語句.(仍是高二dalaoNavi-Awson告訴個人,%%%)
數位dp記憶化搜索寫法
1 #include<bits/stdc++.h> 2 #define in(i) (i=read()) 3 using namespace std; 4 typedef long long lol; 5 lol read() { 6 lol ans=0,f=1; 7 char i=getchar(); 8 while(i<'0'|| i>'9') {if(i=='-') f=-1; i=getchar();} 9 while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();} 10 return ans*f; 11 } 12 lol a,b; 13 lol dp[19][11]; 14 lol bit[19]; 15 lol dfs(lol len,lol pre,lol limit) { 16 if(!len) return 1;//若是搜到最後一位了,返回下界值1 17 if(!limit && dp[len][pre]) return dp[len][pre];//記憶化部分 18 lol maxn=limit?bit[len]:9;//求出最高能夠枚舉到哪一個數字 19 lol ans=0; 20 for(lol i=0;i<=maxn;i++) { 21 if(i!=4 && !(pre && i==2))//若是這一位不爲4而且上一位不爲6且這一位不爲2 22 ans+=dfs(len-1,i==6,limit && i==maxn);//知足條件 23 } 24 if(!limit) dp[len][pre]=ans;//若是沒有限制,表明搜滿了,能夠記憶化,不然就不能 25 return ans; 26 } 27 lol solve(lol a) { 28 memset(bit,0,sizeof(bit)); 29 lol k=0; 30 while(a) {//取出數字的每一位 31 bit[++k]=a%10; 32 a/=10; 33 } 34 return dfs(k,0,1); 35 } 36 int main() 37 { 38 //freopen("number.in","r",stdin); 39 //freopen("number.out","w",stdout); 40 lol t; in(t); 41 for(int i=1;i<=t;i++) { 42 lol a,b; in(a);in(b); 43 cout<<solve(b)-solve(a-1)<<endl;//差分思想 44 } 45 return 0; 46 }
應用
放幾道題目上來