1. 程式人生 > 實用技巧 ># Acwing 1082 數字遊戲

# Acwing 1082 數字遊戲

Acwing 1082 數字遊戲 [數位DP詳解+模板]

數位 DP 問題往往都是這樣的題型,給定一個閉區間\([L,R]\),讓你求這個區間中滿足某種條件的數的總數。

字首和思想,轉化為\(f([0,R])-f([0,L-1])\)求解。

轉化成求\(f(N)\),將上限N轉化成10進位制(根據題意轉化為K進位制,一般是十進位制),列舉從最高位開始列舉N的10進位制的每一位,只要該位的取值小於N的10進位制對應位的取值,那麼這個數必然小於等於N。

一般使用DFS求解。

inline int dfs(int pos,int pre,bool limit,bool lead)一般要記錄當前在處理哪一位,當前位前一位的相關資訊,當前位是否有上限限制,當前位是否有前導0限制(最高位不能取0)

//limit向下一位傳遞的條件
limit==1&&i==a[pos] //當前位有限制,並且當前位取到了限制下的最大值

//lead向下一位傳遞的條件
lead==1&&i==0 //當前位有前導0限制(當前位不能取0),並且當前位取的就是0

例題 Acwing 1082 數字遊戲

題意

求[X,Y]中的數,滿足數字從左到右非下降關係的數的個數,如:123,445.

思路

數位dp經典題。

字首和思想,轉化為\(f([0,R])-f([0,L-1])\)求解。

轉化成求\(f(N)\),將上限N轉化成10進位制(根據題意轉化為K進位制,一般是十進位制),列舉從最高位開始列舉N的10進位制的每一位,只要該位的取值小於N的10進位制對應位的取值,那麼這個數必然小於等於N。

程式碼

#include <bits/stdc++.h>
using namespace std;
//f[i][j]表示前i位,第i+1位為j時的滿足條件的數的總數
//同時充當標記陣列,DFS使用記憶化搜尋,一般將f[][]初始化為-1,遞迴過程中如果出現f[i][j]!=-1,表示這個狀態已經搜尋過,直接返回答案
//本題只需要記錄一個值(數的個數),如果一個狀態需要記錄多個值,f陣列定義為結構體
int f[12][12];

int a[12];//用於儲存上限N的K進位制下的每個數

inline int dfs(int pos,int pre,bool limit){//pos表示當前處理的位,per記錄前一位的資訊,這裡是記錄前一位的值,limit表示當前位是否有上限的限制
    if(!pos)return 1;//遞迴出口
    
    if(!limit&&f[pos][pre]!=-1)return f[pos][pre];//當前位沒有限制,並且已經搜尋過,直接返回答案

    int up=limit?a[pos]:9;//獲取當前位的取值上限,如果有限制,上限為n的對應位的值,否則上限為9
   
    int res=0;
    for(int i=0;i<=up;++i){//列舉當前位的取值
        if(i<pre)continue;//由於要滿足不下降數的限制,如果當前位的取值比上一位小,當前位取值不合法,沒有必要處理當前位取i時後面位的取值
        
        //當前位取值合法,遞迴列舉下一位的取值,下一位上限有限制的條件是limit==1&&a==a[pos](也就是說當前位有限制,並且當前位取了上限值。
        //比如123,百位取1,百位首先肯定是有限制的,因為百位最大取1,此時該位也取到了上限值1,那麼下一位十位也是有限制的,不能超過2)。而如果當前位取0,那麼十位就能隨便取值了0-9都能取。
        res+=dfs(pos-1,i,limit&&i==a[pos]);
    }
    
    //記憶化搜尋,只有在當前狀態沒有任何限制的時候(沒有最高位限制和前導0限制),才表示這個狀態可以通用。
    //return (limit||lead)?res:f[pos][pre]=res;
    return limit?res:f[pos][pre]=res;
}

inline int sol(int n){
    if(!n)return 1;//特殊處理n==0的情況
    
    memset(f,-1,sizeof f);//記憶化陣列兼標記陣列初始化為-1,有些狀態可能本來就是0,所以初始化為-1

    //獲取n的10進位制下的每一位
    int pos=0;
    while (n)a[++pos]=n%10,n/=10;

    return dfs(pos,-1,true);
}
int main(){
    int l,r;
    while (cin>>l>>r){
        cout<<sol(r)-sol(l-1)<<endl;//轉化為字首和求解
    }
    return 0;
}