1. 程式人生 > 其它 >題解HDU6148 Valley Numer(數位DP+深搜DFS)

題解HDU6148 Valley Numer(數位DP+深搜DFS)

技術標籤:ACM動態規劃DPdfs演算法數位DP動態規劃c++

題解HDU6148 Valley Numer[數位DP+深搜DFS]

題目

Description:

眾所周知,度度熊非常喜歡數字。

它最近發明了一種新的數字:Valley Number,像山谷一樣的數字。
在這裡插入圖片描述
當一個數字,從左到右依次看過去數字沒有出現先遞增接著遞減的“山峰”現象,就被稱作 Valley Number。它可以遞增,也可以遞減,還可以先遞減再遞增。在遞增或遞減的過程中可以出現相等的情況。

比如,1,10,12,212,32122都是 Valley Number。

121,12331,21212則不是。

度度熊想知道不大於N的Valley Number數有多少。

注意,前導0是不合法的。

Input:

第一行為T,表示輸入資料組數。

每組資料包含一個數N。

● 1≤T≤200

● 1≤length(N)≤100

e.g.
3
3
14
120

Output:

對每組資料輸出不大於N的Valley Number個數,結果對 1 000 000 007 取模。

e.g.
3
14
119

解析

  • TAG:
    數位DP+DFS
  • DESCRIPITION:
    山谷數是指沒有出現先遞增後遞減的"峰"的數,給定一個大數N,求有多少個小於等於 N 的 Valley Number,模1e9+7
  • INPUT:
    T組測試資料(1≤T≤200),每組一個N(1≤length(N)≤100)
  • SOLUTION:
    (1)總思路:利用dfs進行數位DP的計算與儲存,dp[i][j][k]表示i位的數塊前一個數是j狀態為k的數有多少。狀態k=0表示當前非遞增,1表示當前遞增。
    (2)倆個特殊考慮:一個是噹噹前遞迴出來的數列與上限N重合,一個是全都是0的數不合法,此時狀態pre前導值用10表示。
    (3)正序或倒序處理:注意每一個數塊大小為當前遍歷位到最終位的長度,所以我們可以將字串陣列倒序處理,當然正序也可以,只不過dp陣列表示方式有點不同,具體看程式碼註釋。

參考原始碼

  • 逆向遞迴:
//逆序遞迴
#include<iostream>   
#include<algorithm>
#include<cstring> #define maxn 205 using namespace std; typedef long long ll; const int mod = 1e9 + 7; //dp[i][j][k]表示第i位前一個數是j狀態為k的數有多少。狀態0表示當前非遞增,1表示當前遞增 //注意雖然說是第i位,但由於是倒序的,實際上是第len-i+1位。 ll dp[maxn][15][5]; int num[maxn]; //pos表示當前在第幾位(從後往前) //status=1表示遞增(從後往前) //pre表示前面遞迴的是哪一位(從後往前) //limit表示前面遞迴的是否 ll dfs(int pos, int status, int pre, int limit) { //過了第一位啦,1個alley Number確立,所以return 1 if (pos < 1) return 1; //並且該dp狀態已經存在,並且沒有壓邊界,則可以直接呼叫dp值返回 if (!limit && dp[pos][pre][status] != -1) return dp[pos][pre][status]; //確立遞迴時的該位取值上限 int end = limit ? num[pos] : 9; //此時狀態值為ans,初始化為0 ll ans = 0; if (status) {//原來是遞增 for (int i = pre; i <= end; i++) {//繼續遞增 //limit && i == end表示當前面狀態壓線並且現在遞迴位也壓線時接下來的遞迴狀態也是壓線 ans += dfs(pos - 1, status, i, limit && i == end); ans %= mod; } } else {//原來是遞減 for (int i = 0; i <= end; i++) { if (i > pre) {//該位要遞增 ans += dfs(pos - 1, 1, i, limit && i == end);//改status=1表示原狀態由非遞增變為遞增 ans %= mod; } else {//該位要非遞增 //i是0且前一遞迴位是10,表示這是第一位賦0,因為沒有前導0,00...00就不能山谷數,第一位確認0時需要賦予一個特殊狀態, //即當我們在第一次遍歷時需要置當前位為0時,設定一個10與非首位的0區分開來,這樣最後得到一個全是10的數,結果將其減1就行 if (!i && pre == 10) ans += dfs(pos - 1, 0, 10, limit && i == end); else//非首位正常遞迴 ans += dfs(pos - 1, 0, i, limit && i == end); ans %= mod; } } } //如果不是特殊的壓線情況,就儲存dp值,即第pos位前一個數是pre狀態為status的數有ans那麼多 if (!limit) dp[pos][pre][status] = ans; return ans; } int main() { char s[maxn]; int t; memset(dp, -1, sizeof(dp)); cin >> t; while (t--) { cin >> s; int len = strlen(s); for (int i = 0; i < len; i++) { num[len - i] = s[i] - '0'; } //pos=len,為最後一位 //status=0,遞減 //pre=10,表示是特殊位 //limit=1,最開始時要根據遞迴的首位選擇該位上限值,所以是壓線的 cout << dfs(len, 0, 10, 1) - 1 << endl; } }
  • 正向遞迴:
//正向遞迴
#include<iostream>   
#include<algorithm>  
#include<cstring>  
#define MAXN 200
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
//dp[i][j][k]表示i位的數塊前一個數是j狀態為k的數有多少。狀態0表示當前非遞增,1表示當前遞增
ll dp[MAXN+5][15][5];
int num[MAXN+5];
int len;
//pos表示當前在第幾位(從後往前)
//status=1表示遞增(從後往前)
//pre表示前面遞迴的是哪一位(從後往前)
//limit表示前面遞迴的是否
ll dfs(int pos, int status, int pre, int limit) {
    //過了第一位啦,1個alley Number確立,所以return 1
    if (pos > len)
        return 1;
    //並且該dp狀態已經存在,並且沒有壓邊界,則可以直接呼叫dp值返回
    if (!limit && dp[len-pos+1][pre][status] != -1)
        return dp[len-pos+1][pre][status];
    //確立遞迴時的該位取值上限
    int end = limit ? num[pos] : 9;
  
    //此時狀態值為ans,初始化為0
    ll ans = 0;
    if (status) {//原來是遞增
        for (int i = pre; i <= end; i++) {//繼續遞增
            //limit && i == end表示當前面狀態壓線並且現在遞迴位也壓線時接下來的遞迴狀態也是壓線
            ans += dfs(pos + 1, status, i, limit && i == end);
            ans %= MOD;
        }
    }
    else {//原來是遞減
        for (int i = 0; i <= end; i++) {
            if (i > pre) {//該位要遞增
                ans += dfs(pos + 1, 1, i, limit && i == end);//改status=1表示原狀態由非遞增變為遞增
                ans %= MOD;
            }
            else {//該位要非遞增
                //i是0且前一遞迴位是10,表示這是第一位賦0,因為沒有前導0,00...00就不是山谷數,第一位確認0時需要賦予一個特殊狀態,
                //即當我們在第一次遍歷時需要置當前位為0時,設定一個10與非首位的0區分開來,這樣最後得到一個全是10的數,結果將其減1就行
                if (!i && pre == 10)
                    ans += dfs(pos + 1, 0, 10, limit && i == end);
                else//非首位正常遞迴
                    ans += dfs(pos + 1, 0, i, limit && i == end);
                ans %= MOD;
            }
        }
    }
    //如果不是特殊的壓線情況,就儲存dp值,即第pos位前一個數是pre狀態為status的數有ans那麼多
    if (!limit) {
        dp[len-pos+1][pre][status] = ans;
        //cout << "dp[" << len - pos + 1 << "][" << pre << "][" << status << "]=" << ans << endl;
    }
      
    return ans;
}

int main() {
    char s[MAXN+5];
    int t;
    memset(dp, -1, sizeof(dp));
    cin >> t;
    while (t--) {
        cin >> s;
        len = strlen(s);
        for (int i = 0; i < len; i++) {
            num[i + 1] = s[i] - '0';
        }
        //pos=1,為第一位
        //status=0,遞減
        //pre=10,表示是特殊位
        //limit=1,最開始時要根據遞迴的首位選擇該位上限值,所以是壓線的
        cout << dfs(1, 0, 10, 1) - 1 << endl;
    }
}