全新的動態規劃入門——從維度談起
轉載請註明出處:http://www.cnblogs.com/WABoss/p/DP.html
動態規劃(Dynamic Programming, DP)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法……
(先忘了這個吧)允許我從另一個角度去理解並解釋動態規劃,那麼開始吧。
一、維度
首先說明維度(Dimension)這個概念。這兒有一個很好很有啟發意義的回答:http://www.guokr.com/question/584012/?answer=760222#answer760222。
裡面提到所謂的維度其實就是允許某種東西自由變化的範圍。
比如說,人,人在地球上就有無數個維度,包括人當前所在的經度、緯度和高度,還有人的身高、體重和頭髮顏色之類的,甚至還有時間這個維度。
因此,世間萬物都具有無數個維度。事實上在具體的問題中顯然並不需要考慮這無數個維度,只需分析具體問題所需要的維度。
比如說,一個導航的程式,人的身高、體重和頭髮顏色是無所謂的,而經度和緯度是導航程式最關心的兩個基礎維度,此外還有方向、速度等等維度是和導航相關的,總之具體問題具體分析。
現在聯想一下高階語言裡陣列,如果把多維陣列各個維度拿出來看,是不是與物理上的“維度”的概念有種異曲同工之妙?
-
int dp[n0][n1][n2];
-
比如上面這個多維陣列每個維度n0、n1和n2都可以自由在0到ni-1變化,這正是維度的概念!只不過這些維度是些表面沒有太多意義的連續非負整數。
如果我們賦予它們意義呢?請看下一節,【狀態】。
二、狀態
事物具有多個維度,反之多個維度就能共同描述一個事物,而多個維度的值就描述了事物的狀態。
比如說,導航程式,經度和緯度這兩個維度的值就能描述當前某使用者的狀態,即這個使用者正在某經度某維度的地方。
對應於陣列,經度、緯度兩個維度對應了二維陣列:
-
int dp[MaxLng][MaxLat];
-
如果多維陣列各個維度的值確定後,那麼就相當於確定了在多維陣列中具體哪一個單元格。因此可以這麼說,各個單元格就對應了各個狀態,陣列就是狀態的集合,而狀態就是通過確定陣列各個維度的值定位的!
-
dp[x][y]所對應的單元格就表示人在經度x緯度y的狀態
-
不過,單元格可不僅僅是單元格,因為陣列可以是bool型別、int型別等等,即這個單元格有值的。這個值就是狀態的值,而狀態該是什麼型別的值,取值是多少這取決於具體的問題。
注意,上面說到值有兩個,紅色字,一個是維度的值,一個是狀態的值,其中維度的值描述了具體的狀態。
比如說,上面的導航程式的陣列是int型別,這樣dp[x][y]就可以拿來表示人在到達經度x緯度y這個狀態所需的時間(分鐘數)。
再比如說,如果陣列是bool型別,這樣dp[x][y]就可以拿來表示人能否到達經度x緯度y這個狀態。
在動態規劃問題裡,狀態的值經常是最小值、最大值和方案數等。一般就是從已知的初始狀態的值,通過狀態的轉移,得出最終目標狀態的值。
不過,上面提到的這個“狀態”還不是動態規劃裡的“狀態”,因為還少了一樣東西——請看下一節,【無後效性】。
三、無後效性
動態規劃的狀態必須是無後效性的。下面我用一道題目具體說明什麼是無後效性。
有一樓梯共M級,剛開始時你在第一級,若每次只能跨上一級或二級,要走上第M級,共有多少種走法?
這個問題裡面很容易找到一個維度:樓梯的級數。那麼到達第i級臺階就是一個狀態,簡稱狀態i,問題所需要求的是到達狀態i的方案數,即:
- dp[i]表示到達第i級臺階的方案數
i的取值是從1到M,也就是說有M個狀態。這M個狀態之間是有關係的。比如可以從第1級的臺階直接移動到第2級或者第3級,或者第5級臺階可以從第3級或者第4級臺階直接移動到。
下面就M取5畫一張狀態圖:
這張圖上面的頂點表示的是各個狀態,有向邊(弧)就表示狀態的轉移。事實上這張圖是一張有向無環圖(DAG, Directed Acyclic Graph),即從任何一點出發不可能回到自身。
也就是說在那張圖中狀態i是通過狀態i-1和狀態i-2確定的,並且和狀態i-3、i-4、i-5...沒有一點關係,之後狀態i也不會影響到狀態i-3、i-4、i-5…,i-3、i-4、i-5狀態的值已經是確定好的。
這就是無後效性。動態規劃的狀態必須是無後效性的狀態。
動態規劃本質上可以理解成在一個DAG上的遞推:入度0的狀態點就是初始的狀態,有向邊說明了狀態轉移的方向,通過狀態的轉移按著DAG的拓撲序推出最終目標狀態的值。而由於是DAG,狀態不會重複經過,最多就經過所有的狀態。
具體如何轉移請看下一節,【狀態轉移】。
四、狀態轉移
前面提到了,動態規劃的過程就是從初始狀態轉移到最終的目標狀態,而初始狀態的值是知道的,目標狀態的值就是問題需要的。
還是那一題【超級樓梯】,狀態(的值)是這麼表示的:
-
dp[i]表示到達第i級臺階的方案數
-
而轉移的表示,通常當然不是畫圖,而是寫出狀態轉移方程:
- dp[i] = 1 (i=1)
- dp[i] = dp[i-1] (i=2)
- dp[i] = dp[i-1] + dp[i-2] (i>2)
為什麼是這樣呢?可以從之前畫的狀態圖中理解。
- 首先dp[1]就表示到達臺階1的方案數,一開始就在臺階1,這個狀態的值當然就是1了,即dp[1]=1,而事實上這個也正是初始狀態;
- 然後dp[i]=dp[i-1](i=2),也就是說dp[2]=dp[1],因為走到臺階2只有一種策略就會從臺階1走來,因而到達臺階2的方案數就等於達到臺階1的方案數,也就是1;
- 最後dp[i]=dp[i-1]+dp[i-2](i>2),這個從語義上理解是這樣的:走到i-1的方案數是dp[i-1],然後向上走一步就到了i,因而狀態i的方案數就有dp[i-1];而走到i-2的方案數是dp[i-2],然後向上走2步就到了i,因而狀態i的方案數又可以有dp[i-2]種;總共就是dp[i-1]+dp[i-2]個方案數。
那麼有了狀態轉移方程,就可以動手寫程式實現了。具體程式的實現多種多樣,但要注意的是狀態的值一定要按拓撲序依次求出。下面我講講講三種方式:“人人為我”、“我為人人”和記憶化搜尋。其中“人人為我”和“我為人人”這兩個是我從北大ACM-ICPC暑期課的課件中學到的。
- “人人為我”
這個很常見,就是上面轉移方程表示的那樣。
狀態圖大概可以這麼看:
比如可以看到狀態5就是從3和4轉移過來的。
寫成程式就是這樣:
- #include<cstdio>
- using namespace std;
- int dp[41];
- int main(){
- dp[1]=1; dp[2]=1;
- for(int i=3; i<=40; ++i){
- dp[i]=dp[i-1]+dp[i-2];
- }
- int N,M;
- scanf("%d",&N);
- while(N--){
- scanf("%d",&M);
- printf("%d\n",dp[M]);
- }
- return 0;
- }
- “我為人人”
這個是從當前狀態順著推過去,更新能到達狀態的值。
這個實現起來挺直觀的,因為是順著往前推。
狀態圖可以這麼看:
比如可以看到狀態1更新到2和3。
寫成程式是這樣的:
- #include<cstdio>
- using namespace std;
- int dp[44];
- int main(){
- dp[1]=1;
- for(int i=1; i<40; ++i){
- dp[i+1]+=dp[i];
- dp[i+2]+=dp[i];
- }
- int N,M;
- scanf("%d",&N);
- while(N--){
- scanf("%d",&M);
- printf("%d\n",dp[M]);
- }
- return 0;
- }
- 記憶化搜尋
一般都是用DFS實現,就是用搜尋當前狀態能從哪兒轉移過來。
另外開一個陣列記錄搜尋過的狀態的值,避免重複搜尋,時間複雜度就不會是指數級的了,而是多項式級。
有時候用記憶化搜尋很直觀,而且記憶化搜尋還可以避免搜尋無意義、不需要的狀態,從而使效率提升。
這個直接上程式碼吧:
- #include<cstdio>
- using namespace std;
- int dp[44];
- int dfs(int i){
- if(dp[i]!=0) return dp[i];
- return dp[i]=dfs(i-1)+dfs(i-2);
- }
- int main(){
- dp[1]=1; dp[2]=1;
- int N,M;
- scanf("%d",&N);
- while(N--){
- scanf("%d",&M);
- printf("%d\n",dfs(M));
- }
- return 0;
- }
相關文章
- DP 動態規劃入門 一維陣列動態規劃陣列
- 動態規劃入門篇動態規劃
- 動態規劃法(一)從斐波那契數列談起動態規劃
- 禮物的最大價值(一維動態規劃&二維動態規劃)動態規劃
- 淺談動態規劃動態規劃
- 機器學習入門_從機器學習談起機器學習
- 談一談動態規劃和dfs動態規劃
- 一文帶你入門動態規劃動態規劃
- 動態規劃中初識狀態壓縮(入門)動態規劃
- 一維動態規劃和二維動態規劃中兩道經典題目動態規劃
- 動態規劃入門——動態規劃與資料結構的結合,在樹上做DP動態規劃資料結構
- 一維動態規劃總結動態規劃
- 為什麼你學不過動態規劃?告別動態規劃,談談我的經驗動態規劃
- 從“股票問題”談動態規劃問題的解決思路動態規劃
- LeetCode入門指南 之 動態規劃思想LeetCode動態規劃
- 【動態規劃(一)】動態規劃基礎動態規劃
- 動態規劃動態規劃
- 聊聊不太符合常規思維的動態規劃演算法動態規劃演算法
- 淺談動態規劃以及相關的股票問題動態規劃
- 機器學習入門規劃機器學習
- 機器學習入門第一課:從高中課本談起機器學習
- 動態規劃之數的劃分動態規劃
- 動態規劃分析動態規劃
- 動態規劃(DP)動態規劃
- 動態規劃初步動態規劃
- 模板 - 動態規劃動態規劃
- 動態規劃法動態規劃
- 集合上的動態規劃動態規劃
- 動態規劃入門 E – 免費餡餅 (dp的另一個應用)動態規劃
- 動態規劃使用一維陣列要注意的問題動態規劃陣列
- 【動態規劃】用一維和二維解決不同路徑動態規劃
- 演算法系列-動態規劃(1):初識動態規劃演算法動態規劃
- 有關動態規劃動態規劃
- 動態規劃小結動態規劃
- 動態規劃初級動態規劃
- 動態規劃講義動態規劃
- 好題——動態規劃動態規劃
- 3.動態規劃動態規劃