全新的動態規划入門——從維度談起


轉載請注明出處: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這個狀態。

 

在動態規划問題里,狀態的值經常是最小值、最大值和方案數等。一般就是從已知的初始狀態的值,通過狀態的轉移,得出最終目標狀態的值。

不過,上面提到的這個“狀態”還不是動態規划里的“狀態”,因為還少了一樣東西——請看下一節,【無后效性】。

 

 

三、無后效性


動態規划的狀態必須是無后效性的。下面我用一道題目具體說明什么是無后效性

 

HDU2041 超級樓梯

有一樓梯共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)

 

為什么是這樣呢?可以從之前畫的狀態圖中理解。

  1. 首先dp[1]就表示到達台階1的方案數,一開始就在台階1,這個狀態的值當然就是1了,即dp[1]=1,而事實上這個也正是初始狀態;
  2. 然后dp[i]=dp[i-1](i=2),也就是說dp[2]=dp[1],因為走到台階2只有一種策略就會從台階1走來,因而到達台階2的方案數就等於達到台階1的方案數,也就是1;
  3. 最后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;
}

 

 

 

 

五、最后


如果有什么地方說得不好或者不對的歡迎交流。

博客地址:http://www.cnblogs.com/WABoss

或者QQ聯系:2322271369

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM