《動態規劃》 ACM 動態規劃例題詳解

2021-07-16 22:31:38 字數 3527 閱讀 4241

描述

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

(圖1)

圖1給出了乙個數字三角形。從三角形的頂部到底部有很多條不同的路徑。對於每條路徑,把路徑上面的數加起來可以得到乙個和,你的任務就是找到最大的和。

注意:路徑上的每一步只能從乙個數走到下一層上和它最近的左邊的那個數或者右邊的那個數。

輸入

輸入的是一行是乙個整數n (1 < n <= 100),給出三角形的行數。下面的n行給出數字三角形。數字三角形上的數的範圍都在0和100之間。

輸出

輸出最大的和。

樣例輸入

5

73 8

8 1 0

2 7 4 4

4 5 2 6 5

樣例輸出

30
解題思路:(遞迴)

以d( r, j)表示第r 行第 j 個數字(r,j 都從1 開始算),以maxsum(r, j) 代表從第 r 行的第 j 個數字到底邊的最佳路徑的數字之和,則本題是要求 maxsum(1, 1) 。

從某個d(r, j)出發,顯然下一步只能走d(r+1, j)或者d(r+1, j+1)。如果走d(r+1, j),那麼得到的maxsum(r, j)就是maxsum(r+1, j) + d(r, j);如果走d(r+1, j+1),那麼得到的maxsum(r, j)就是maxsum(r+1, j+1) + d(r, j)。所以,選擇往**走,就看maxsum(r+1, j)和maxsum(r+1, j+1)哪個更大了。

#include#define max_num 100

int d[max_num + 10][max_num + 10];

int n;

int maxsum(int r, int j)

int nsum1 = maxsum(r+1, j);

int nsum2 = maxsum(r+1, j+1);

if(nsum1 > nsum2)else

}int main(void)

} printf("%d",maxsum(1,1));

return 0;

}

但是提交結果超時

上面的程式,效率非常低,在n 值並不大,比如n=100 的時候,就慢得幾乎永遠算不出結果了。

為什麼會這樣呢?是因為過多的重複計算。

我們不妨將對maxsum 函式的一次呼叫稱為一次計算。那麼,每次計算maxsum(r, j)的時候,都要計算一次maxsum(r+1, j+1),而每次計算maxsum(r, j+1)的時候,也要計算一次maxsum(r+1, j+1)。重複計算因此產生。

在題目中給出的例子裡,如果我們將maxsum(r, j)被計算的次數都寫在位置(r, j),那麼就能得到下面的三角形:

1、從上圖可以看出,最後一行的計算次數總和是16,倒數第二行的計算次數總和是8。不難總結出規律,對於n行的三角形,總的計算次數是2^0+2^1+2^2+…+2^(n-1)=2^n-1。當n=100 時,總的計算次數是乙個讓人無法接受的大數字。

2、既然問題出在重複計算,那麼解決的辦法,當然就是,乙個值一旦算出來,就要記住,以後不必重新計算。即第一次算出maxsum(r,j)的值時,就將該值存放起來,下次再需要計算maxsum(r,j)時,直接取用存好的值即可,不必再次呼叫maxsum進行函式遞迴計算了。這樣,每個maxsum(r,j)都只需要計算1次即可,那麼總的計算次數(即呼叫maxsum函式的次數)就是三角形中的數字總數,即1+2+3+…+n= n(n+1)/2。

3、如何存放計算出來的maxsum(r,j)值呢?顯然,用乙個二維陣列amaxsum[n][n]就能解決。amaxsum[r][j]就存放maxsum(r,j)的計算結果。下次再需要maxsum(r,j)的值時,不必再呼叫maxsum函式,只需直接取amaxsum[r][j]的值即可。

#include #include #define max_num 100

int d[max_num + 10][max_num + 10];

int n;

int amaxsum[max_num + 10][max_num + 10];

int maxsum( int r, int j)

int main(void)

這種將乙個問題分解為子問題遞迴求解,並且將中間結果儲存以避免重複計算的辦法,就叫做「動態規劃」。動態規劃通常用來求最優解,能用動態規劃解決的求最優解問題,必須滿足,最優解的每個區域性解也都是最優的。以上題為例,最佳路徑上面的每個數字到底部的那一段路徑,都是從該數字出發到達到底部的最佳路徑。

實際上,遞迴的思想在程式設計時未必要實現為遞迴函式。在上面的例子裡,有遞推公式:

因此,不需要寫遞迴函式,從amaxsum[n-1]這一行元素開始向上逐行遞推,就能求得amaxsum[1][1]的值了。

動態規劃解題的一般思路 

1、許多求最優解的問題可以用動態規劃來解決。

2、首先要把原問題分解為若干個子問題。注意單純的遞迴往往會導致子問題被重複計算,用動態規劃的方法,子問題的解一旦求出就要被儲存,所以每個子問題只需求解一次。

3、子問題經常和原問題形式相似,有時甚至完全一樣,只不過規模從原來的n變成了n-1,或從原來的n×m變成了n×(m-1)……等等。

4、找到子問題,就意味著找到了將整個問題逐漸分解的辦法。

5、分解下去,直到最底層規模最小的的子問題可以一目了然地看出解。

6、每一層子問題的解決,會導致上一層子問題的解決,逐層向上,就會導致最終整個問題的解決。

7、如果從最底層的子問題開始,自底向上地推導出乙個個子問題的解,那麼程式設計的時候就不需要寫遞迴函式。

n用動態規劃解題時,將和子問題相關的各個變數的一組取值,稱之為乙個「狀態」。乙個「狀態」對應於乙個或多個子問題,所謂某個「狀態」下的「值」,就是這個「狀態」所對應的子問題的解。

n比如數字三角形,子問題就是「從位於(r,j)數字開始,到底邊路徑的最大和」。這個子問題和兩個變數r和j相關,那麼乙個「狀態」,就是r,j 的一組取值,即每個數字的位置就是乙個「狀態」。該「狀態」所對應的「值」,就是從該位置的數字開始,到底邊的最佳路徑上的數字之和。

n定義出什麼是「狀態」,以及在該 「狀態」下的「值」後,就要找出不同的狀態之間如何遷移――即如何從乙個或多個「值」已知的「狀態」,求出另乙個「狀態」的「值」。狀態的遷移可以用遞推公式表示,此遞推公式也可被稱作「狀態轉移方程」。

動態規劃例題

題目描述 有一條很長的數軸,一開始你在0的位置。接下來你要走n步,第i步你可以往右走ai或者bi。問n步之後,0到m的每個位置,能不能走到?輸入格式 第一行,兩個整數n,m。接下來n行,每行兩個整數ai,bi。輸出格式 一行,一共m 1個數,每個數都是0或1表示能否走到,數字之間不用空格隔開。樣例輸...

動態規劃及其動態規劃經典例題

動態規劃是最重要 最經典的演算法之一,學好動態規劃對我們十分重要,掌握動態規劃對解決某些問題會起到事半功倍的效果。特點 可以把原始問題劃分為一系列子問題 求解每個子問題僅一次,並將其結果儲存到乙個表中,以後用到時直接訪問,不重複計算,節省時間。自底向上地計算 適用範圍 原問題可以分為多個相關子問題,...

acm 動態規劃

學習參考 就是倒推 尋找遞推式 難點 然後用陣列將資料計算出來 最後直接呼叫得到答案 01揹包問題 for i 0 i 例題一 洛谷oj 開心的金明 include include using namespace std const int max 100001 long long dp max i...