hdu 2089 不要62 數位dp入門
阿新 • • 發佈:2018-02-03
stream 然而 != 1的個數 tdi 記憶化 代碼 pan +=
題意:求一個範圍內的數字,約束條件為不含有數字4以及62
- 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));
- }
- }
這裏仿照一個大佬的思路開始入門通用一點的數位dp 值得註意的一點是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狀態唯一
上代碼:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; int a[1010]; int dp[20][2]; int dfs(int pos,int pre,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 sum=0; for(int i=0;i<=up;i++) { if(pre==6 && i==2 || i==4) continue; sum+=dfs(pos-1,i,i==6,limit & i==a[pos]); } if(!limit) dp[pos][sta]=sum; // return sum; } int solve(int x) { int pos=0; while(x) { a[pos++]=x%10; x/=10; } return dfs(pos-1,-1,0,true); } int main() { int n,m; while(cin>>n>>m) { if(n==0 && m==0) break; memset(dp,-1,sizeof(dp)); cout<<solve(m)-solve(n-1)<<endl; } return 0; }
hdu 2089 不要62 數位dp入門