1. 程式人生 > >hdu 2089 不要62 數位dp入門

hdu 2089 不要62 數位dp入門

stream 然而 != 1的個數 tdi 記憶化 代碼 pan +=

題意:求一個範圍內的數字,約束條件為不含有數字4以及62
  1. typedef long long ll;
  2. int a[20];
  3. ll dp[20][state];//不同題目狀態不同
  4. ll dfs(int pos,/*state變量*/,bool lead/*前導零*/,bool limit/*數位上界變量*/)//不是每個題都要判斷前導零
  5. {
  6. //遞歸邊界,既然是按位枚舉,最低位是0,那麽pos==-1說明這個數我枚舉完了
  7. if(pos==-1) return 1;/*這裏一般返回1,表示你枚舉的這個數是合法的,那麽這裏就需要你在枚舉時必須每一位都要滿足題目條件,也就是說當前枚舉到pos位,一定要保證前面已經枚舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */
  8. //第二個就是記憶化(在此前可能不同題目還能有一些剪枝)
  9. if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
  10. /*常規寫法都是在沒有限制的條件記憶化,這裏與下面記錄狀態是對應,具體為什麽是有條件的記憶化後面會講*/
  11. int up=limit?a[pos]:9;//根據limit判斷枚舉的上界up;這個的例子前面用213講過了
  12. ll ans=0;
  13. //開始計數
  14. for(int i=0;i<=up;i++)//枚舉,然後把不同情況的個數加到ans就可以了
  15. {
  16. if() ...
  17. else if()...
  18. ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變量傳參都是這樣寫的
  19. /*這裏還算比較靈活,不過做幾個題就覺得這裏也是套路了
  20. 大概就是說,我當前數位枚舉的數是i,然後根據題目的約束條件分類討論
  21. 去計算不同情況下的個數,還有要根據state變量來保證i的合法性,比如題目
  22. 要求數位上不能有62連續出現,那麽就是state就是要保存前一位pre,然後分類,
  23. 前一位如果是6那麽這意味就不能是2,這裏一定要保存枚舉的這個數是合法*/
  24. }
  25. //計算完,記錄狀態
  26. if(!limit && !lead) dp[pos][state]=ans;
  27. /*這裏對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裏就是lead就完全不用考慮了*/
  28. return ans;
  29. }
  30. ll solve(ll x)
  31. {
  32. int pos=0;
  33. while(x)//把數位都分解出來
  34. {
  35. a[pos++]=x%10;//個人老是喜歡編號為[0,pos),看不慣的就按自己習慣來,反正註意數位邊界就行
  36. x/=10;
  37. }
  38. return dfs(pos-1/*從最高位開始枚舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛
  39. }
  40. int main()
  41. {
  42. ll le,ri;
  43. while(~scanf("%lld%lld",&le,&ri))
  44. {
  45. //初始化dp數組為-1,這裏還有更加優美的優化,後面講
  46. printf("%lld\n",solve(ri)-solve(le-1));
  47. }
  48. }
這裏仿照一個大佬的思路開始入門通用一點的數位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入門