動態規劃入門
動態規劃入門
什麽是動態規劃?
動態規劃(dynamic programming)是求解決策過程(decision process)最優化的數學方法。把多階段過程轉化為一系列單階段問題,利用各階段之間的關系,逐個求解,創立了解決這類過程優化問題的新方法——動態規劃。
動態規劃可以分為幾類:
線性動規:攔截導彈,合唱隊形,挖地雷,建學校,劍客決鬥等;
區域動規:石子合並, 加分二叉樹,統計單詞個數,炮兵布陣等;
樹形動規:貪吃的九頭龍,二分查找樹,聚會的歡樂,數字三角形等;
背包問題:01背包問題,完全背包問題,分組背包問題,二維背包,裝箱問題,擠牛奶等; 同時,動態規劃需要滿足一定的條件,主要有兩個:將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱為無後效性。 什麽是狀態轉移方程?
給定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階段的狀態轉移規律,稱為狀態轉移方程。
題目描述
觀察下面的數字金字塔。
寫一個程序來查找從最高點到底部任意處結束的路徑,使路徑經過數字的和最大。每一步可以走到左下方的點也可以到達右下方的點。
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代碼相對來說要更簡潔,但思維難度較高。
入門篇就講到這裏!
(完)
動態規劃入門