數字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...