1. 程式人生 > >動態規劃入門

動態規劃入門

記憶 重要 滿足 最優化策略 ogr 描述 背包 決策 背包問題

動態規劃入門

什麽是動態規劃?

動態規劃(dynamic programming)是求解決策過程(decision process)最優化的數學方法。把多階段過程轉化為一系列單階段問題,利用各階段之間的關系,逐個求解,創立了解決這類過程優化問題的新方法——動態規劃。

動態規劃可以分為幾類:

線性動規:攔截導彈,合唱隊形,挖地雷,建學校,劍客決鬥等;

區域動規:石子合並, 加分二叉樹,統計單詞個數,炮兵布陣等;

樹形動規:貪吃的九頭龍,二分查找樹,聚會的歡樂,數字三角形等;

背包問題:01背包問題,完全背包問題,分組背包問題,二維背包,裝箱問題,擠牛奶等; 同時,動態規劃需要滿足一定的條件,主要有兩個:
1.最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,余下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。 2.無後效性
將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱為無後效性。 什麽是狀態轉移方程?

給定k階段狀態變量x(k)的值後,如果這一階段的決策變量一經確定,第k+1階段的狀態變量x(k+1)也就完全確定,即x(k+1)的值隨x(k)和第k階段的決策u(k)的值變化而變化,那麽可以把這一關系看成(x(k),u(k))與x(k+1)確定的對應關系,用x(k+1)=Tk(x(k),u(k))表示。這是從k階段到k+1階段的狀態轉移規律,稱為狀態轉移方程。

(以上摘自百度百科) 動態規劃的搜索實現 動態規劃其實可以用我們很常見的搜索方法來實現。為什麽搜索這麽慢?因為在搜索過程中,有很多數據被重復計算了很多次,極大地浪費了時間。如果我們定義一個數組vis,表示該數據有沒有被計算過,如果被計算過,那就直接return;沒有計算過就計算。這樣一來搜索時間有了質的飛躍,因為如果說以前的搜索是一個健忘癥,計算一次忘一次,那麽現在每個數被算出來的同時,就被牢牢記住了,不會再算。這種搜索方法因此得名——記憶化搜索。記憶化搜索其實就是動態規劃,是動態規劃的一種實現方式。記憶化搜索的時間復雜度與用狀態轉移方程的dp的時間復雜度相差不大,編碼較尋常dp較長,但是思維難度低,在個別題目中甚至更優於尋常dp,可以排除一些無效狀態。更重要的是搜索還可以剪枝,可能剪去大量不必要的狀態,因此在空間開銷上往往比動態規劃要低很多。所以當你不會打dp時,記憶化搜索是一個很好的選擇。
例題:

題目描述

觀察下面的數字金字塔。

寫一個程序來查找從最高點到底部任意處結束的路徑,使路徑經過數字的和最大。每一步可以走到左下方的點也可以到達右下方的點。

         7 
      3   8 
    8   1   0 
  2   7   4   4 
4   5   2   6   5 

在上面的樣例中,從7 到 3 到 8 到 7 到 5 的路徑產生了最大

輸入輸出格式

輸入格式:

第一個行包含 R(1<= R<=1000) ,表示行的數目。

後面每行為這個數字金字塔特定行包含的整數。

所有的被供應的整數是非負的且不大於100。

輸出格式:

單獨的一行,包含那個可能得到的最大的和。

輸入輸出樣例

輸入樣例#1:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 
輸出樣例#1:
30

說明

題目翻譯來自NOCOW。

USACO Training Section 1.5

這個題可以算是動態規劃的開山鼻祖了,如果我們不知道dp這種東西,那你會怎麽做?

方法一:爆搜

我們可以爆搜,搜索從頂向下的每一條路徑

#include<cstdio>

using namespace std;

const int maxn = 1010;

int r,a[maxn][maxn];

int max(int x,int y) {
    return x > y ? x : y;
}

inline void input() {
    scanf("%d",&r);
    for(int i = 0; i < r; i++)
        for(int j = 0; j <= i; j++)
            scanf("%d",&a[i][j]);
    return;
}

inline int maxnumber(int i,int j) {
    if (i == r)
        return a[i][j];
    return max(maxnumber(i + 1,j),maxnumber(i + 1,j + 1)) + a[i][j];
}

int main() {
    input();
    printf("%d",maxnumber(0,0));
    return 0;
}

這樣一來我們的搜索總數達到了2^(d-1),其中d是三角形總層數

肯定是不可取的

方法二:記憶化搜索

定義一個數組vis,表示該數據有沒有被計算過,如果被計算過,那就直接return;沒有計算過就計算。

#include<cstdio>
#include<cstring>

using namespace std;

const int maxn = 1010;

int r,a[maxn][maxn];
int vis[maxn][maxn];

int max(int x,int y) {
    return x > y ? x : y;
}

int maxnumber(int i,int j) {
    if (vis[i][j] == -1) {
        if (i == r)
            vis[i][j] = a[i][j];
        else 
            vis[i][j] = max(maxnumber(i + 1,j),maxnumber(i + 1,j + 1)) + a[i][j];
    }
       return vis[i][j];
}

inline void input() {
    scanf("%d",&r);
    memset(vis,-1,sizeof(vis));
    for(int i = 0; i < r; i++)
        for(int j = 0; j <= i; j++) 
            scanf("%d",&a[i][j]);
    maxnumber(0,0);
}

int main() {
    input();
    printf("%d",vis[0][0]);
    return 0;
}

時間復雜度降成了O(n) !

方法三:動態規劃

動態規劃問題需要設置狀態(一維或多維數組)和轉移方程

還是以數字三角形問題為例

我們設f[i][j]表示第i行第j列的點走到底層的最優答案。

這時的f[i][j]就是狀態

f[i][j] = a[i][j] + max(f[i+1][j], f[i+1][j+1])

由子問題推導原問題的轉移式就是狀態轉移方程

#include<cstdio>

using namespace std;

const int MAXN = 1010;

int r,a[MAXN][MAXN],f[MAXN][MAXN];

int max(int x,int y) {
    return x > y ? x : y;
}

int main() {
    scanf("%d",&r);
    for (int i = 1; i <= r; i++) 
        for (int j = 1; j <= i; j++) {
            scanf("%d",&a[i][j]);
            f[i][j] = a[i][j];
        }
    for (int i = r - 1; i > 0; i--)
        for (int j = 1; j <= i; j++) 
            f[i][j] += max(f[i + 1][j],f[i + 1][j + 1]);
    printf("%d",f[1][1]);
    return 0;
}

dp代碼相對來說要更簡潔,但思維難度較高。

入門篇就講到這裏!

(完)

動態規劃入門