數字DP入門 數字DP模板

2021-08-07 04:25:11 字數 3528 閱讀 5071

數字dp是一種計數用的dp,一般就是要統計乙個區間[le,ri]內滿足一些條件數的個數。所謂數字dp,字面意思就是在數字上進行dp咯。數字還算是比較好聽的名字,數字的含義:乙個數有個位、十位、百位、千位......數的每一位就是數字啦!

之所以要引入數字的概念完全就是為了dp。數字dp的實質就是換一種暴力列舉的方式,使得新的列舉方式滿足dp的性質,然後記憶化就可以了。

兩種不同的列舉:對於乙個求區間[le,ri]滿足條件數的個數,最簡單的暴力如下:

[cpp]view plain

copy

for(inti=le;i<=ri;i++)  

if(right(i)) ans++;  

然而這樣列舉不方便記憶化,或者說根本無狀態可言。

新的列舉:控制上界列舉,從最高位開始往下列舉,例如:ri=213,那麼我們從百位開始列舉:百位可能的情況有0,1,2(覺得這裡列舉0有問題的繼續看)

然後每一位列舉都不能讓列舉的這個數超過上界213(下界就是0或者1,這個次要),當百位列舉了1,那麼十位列舉就是從0到9,因為百位1已經比上界2小了,後面數字列舉什麼都不可能超過上界。所以問題就在於:當高位列舉剛好達到上界是,那麼緊接著的一位列舉就有上界限制了。具體的這裡如果百位列舉了2,那麼十位的列舉情況就是0到1,如果前兩位列舉了21,最後一位之是0到3(這一點正好對於**模板裡的乙個變數limit 專門用來判斷列舉範圍)。最後乙個問題:最高位列舉0:百位列舉0,相當於此時我列舉的這個數最多是兩位數,如果十位繼續列舉0,那麼我列舉的就是以為數咯,因為我們要列舉的是小於等於ri的所以數,當然不能少了位數比ri小的咯!(這樣列舉是為了無遺漏的列舉,不過可能會帶來乙個問題,就是前導零的問題,模板裡用lead變數表示,不過這個不是每個題目都是會有影響的,可能前導零不會影響我們計數,具體要看題目)

由於這種新的列舉只控制了上界所以我們的main函式總是這樣:

[cpp]view plain

copy

intmain()  

w_w 是吧!統計[1,ri]數量和[1,le-1],然後相減就是區間[le,ri]的數量了,這裡我寫的下界是1,其實0也行,反正相減後就沒了,注意題目中le的範圍都是大於等於1的(不然le=0,再減一就g_g了)

在講例題之前先講個基本的動態模板(先看後面的例題也行):dp思想,列舉到當前位置pos,狀態為state(這個就是根據題目來的,可能很多,畢竟dp千變萬化)的數量(既然是計數,dp值顯然是儲存滿足條件數的個數)

[cpp]view plain

copy

typedeflonglongll;  

inta[20];  

ll dp[20][state];//不同題目狀態不同

ll dfs(intpos,/*state變數*/,boollead/*前導零*/,boollimit/*數字上界變數*/)//不是每個題都要判斷前導零

//計算完,記錄狀態

if(!limit && !lead) dp[pos][state]=ans;  

/*這裡對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裡就是lead就完全不用考慮了*/

returnans;  

}  ll solve(ll x)  

returndfs(pos-1/*從最高位開始列舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛

}intmain()  

}  例題

【hdu2089】不要六二

中文題面,題意很好理解,這裡不再贅述

這是數字dp的入門題,可以好好理解數字dp的原理和基本框架,上面這個模板很詳細,可以解決一般的數字dp問題,這題很簡單,直接上**了:

#include #include #include #include #include #include #include #include #include using namespace std;

typedef long long ll;

int a[20];

ll dp[20][2];

ll dfs(int pos, int pre, int state, bool limit)//不是每個題都要判斷前導零

if(!limit) dp[pos][state]=ans;

return ans;

}ll solve(ll x)

return dfs(pos - 1, 0, 0, true);

}int main()

return 0;

}

【hdu6148】valley number

這也是一道中文題,題意很好懂就是數字從左到右看不要有先遞增後遞減的情況即可

這道題只要修改一下上面的模板就可以了,數字dp題的關鍵在於如何分析下乙個數字的情況

ll dfs(int pos, int pre, int turn, bool limit, bool invalid)

//這條語句中pos是要dp的位置,pre,turn,limit,invalid...這些都是前提條件,或者

//說是之前位置上確定一些數之後的狀態,而這個dfs要進行的就是在這個狀態下繼續

//確定下一位的數字

同樣直接看**吧,這種題關鍵在於你要理解如何確定每一位數字的過程

#include #include #include #include #include #include #include #include #include using namespace std;

typedef long long ll;

const ll m = 1e9 + 7;

int a[105];

ll dp[105][10][3];

ll dfs(int pos, int pre, int turn, bool limit, bool invalid)//turn:0不清楚,1下降,2上公升

ans %= m;

if(!limit) dp[pos][pre][turn]=ans;

return ans;

}char str[105];

int main()

return 0;

}

這就是數字dp的入門了,之後繼續學習數字dp更美妙的高階藝術吧!

參考資料:

數字dp 模板

dp pos,狀態變數.限制布林 if limit dp 狀態 a 已經求出對應狀態值下的結果了 記錄下來 return a 所以 數字dp其實就是一種求解有關於l到r有多少個符合條件的數目類似的統計問題的解題思路 一遍遍數字列舉太慢 不如我們根據條件列舉數字 數字dp本質上是記憶化搜尋 我們需要在...

數字DP模板

數字dp問題,大多是統計數量,通常用按位處理的方法解決。具體為 詢問 l,r l,r l,r 中滿足某一條件的數。利用字首和的思想,我們可以把問題簡化,即轉化為詢問 1,r 1,r 1,r 和 1,l 1 1,l 1 1,l 1 實現時,常常使用記憶化搜尋,由於有很多限制條件,所以常常增加幾維狀態,...

數字dp模板

typedef long long ll int a 20 ll dp 20 state 不同題目狀態不同 ll dfs int pos,state變數 bool lead 前導零 bool limit 數字上界變數 不是每個題都要判斷前導零 計算完,記錄狀態 if limit lead dp po...