數位dp小結

數位dp小結

關於limit的問題

在數位dp中,limit的作用主要有兩個。

  • 控制枚舉的界限,倘若沒有界限,每一位的枚舉範圍都是0-9. 但如果有界限,那麼可能不能取到9. 例如求1到311範圍內不含有連續兩個1的個數。當百位枚舉到了3,那麼如果你的十位只能枚舉0-2.
  • 控制剪枝的合法性: 很明顯,相比於暴力搜索,數位dp唯一的優勢就是剪枝。但剪枝往往會帶來一些錯誤。在數位dp中,我們進行剪枝的一個前提是limit0,也就是當前位枚舉沒有限制時才能進行剪枝。 爲什麼?請先看這個例子:題目依然是求1到321中,不含連續兩個1的個數。當百位枚舉爲0,十位枚舉爲1時,當枚舉個位時,除了個位枚舉1不滿足題意外,其他9個數都滿足,所以dp[0][1]=9。而當百位枚舉到3時,十位枚舉1,此時我們已經記憶了dp[0][1]=9,但很明顯,此時只有310,滿足題意。 爲什麼會出現這樣的情況? 其實一個明顯的原因就是:我們記憶化時的狀態,僅僅只記憶了limit1,也就是沒有限制時的狀態。該算法認爲,有限制條件的數是很少的,所以對於有限制條件的數,我們都是直接暴力搜索的。

理解前面這段話可能比較抽象,可以結合下面這段模板代碼來理解。這段代碼來自:數位dp總結

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的核心在於狀態表示。狀態表示的原則是:使用空間儘可能小;保證狀態表示的唯一性。

關於前導0

前導0指的是: 例如枚舉1到1000之間數,數位dp是從最高位,千位開始枚舉千位從0枚舉到1.當千位爲0時,就是一個前導0,例如,001其實表示的就是1.
關於前導0的題,可以參考leetcode

發佈了408 篇原創文章 · 獲贊 166 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章