1、什麼是動態規劃?
動態規劃的定義,引自維基百科:
dynamicprogramming is a method for solving a complex problem bybreakingit down into a collection of simpler subproblems.
動態規劃是對某類最優化問題的解決方法。動態規劃尋找一種對問題的觀察角度,構造出問題的子問題,使原問題能夠以遞推的方式去解決。
2、動態規劃原理
適合應用動態規劃方法求解的最優化問題應該具備兩個要素:最優子結構和子問題重疊。
下面由”求一個數列的最長上升(遞增)子數列長度(LIS)”問題來說明:
給定一個長度爲6的數列:1 5 6 2 7 8
這個數列的的LIS是1 5 6 7 8,長度爲5
1 2 7 8和5 6 7 8同樣爲數列的子上升數列,但它們都不是最長上升子數列。
最優子結構
用動態規劃方法求解最優化問題的第一步是刻畫出最優解的結構。如果一個問題的最優解包含其子問題的最優解,就稱此問題具有最優子結構性質。
重新定義上述問題:
給定一個長度爲N的數列;
設Fk爲:以數列第K項結尾的LIS長度;
則F1~N中的最大值即爲所求。
對於求解Fk,每個F1~k-1都是Fk的子問題。不難證明,以k項結尾的LIS包含着以1~k-1項結尾的LIS。
需要注意的是,定義最優子結構有個隱含條件,就是子結構的無後效性。從F1_k-1可以求得Fk,也就是每個階段的最優解可以從之前某個階段的最優解直接得到,則稱這個問題具有最優子結構。
而同時,不管之前某個階段的最優解是如何得到的,不管F1~k-1是如何得到的,因爲要求Fk時,只需知道F1~k-1的結果,而並不關心它是如何求出來的。
子問題重疊
重疊的子問題實際上是同一個問題,只是作爲不同問題的子問題出現。通過記錄這些重疊的子問題,避免問題的反覆求解,從而達到以空間換時間的效果。
3、動態規劃的具體實現
題意:
天天酷跑,給一個2*n的格子,初始時在(2,1)這個點上,每次兩種操作
從(2,i)移動到(2,i+1)
從(2,i)跳起,經過(1,i+1)(1,i+2)跳到(2,i+3)
當到達(1,n)或者(2,n)的時候,遊戲結束,每個格子上有一個分數,遊戲總分數就是經過的格子分數之和,求能獲得的分數的最大值
Sample:
0 0 1 1 0
2 1 2 0 1答案6
思路:
DP[i]表示,到第i個格子而且在地面時能獲得的最高分數,答案就是DP[n]
狀態轉移:
1.從i-1個格子直接走過來dp[i]← dp[i-1] + score[2][i]
2.從i-3個格子跳過來dp[i]← dp[i-3] + score[1][i-2] + score[1][i-1] + score[2][i]
自頂向下,通過遞歸實現
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[2][100005];
int d[100005];
int vis[100005];
int dp(int i)
{
if(vis[i]) return d[i];
d[i] = a[1][i];
if(i == 0)
return a[1][0];
if(i <= 2)
d[i] += dp(i-1);
else
{
d[i] += max(dp(i-1) , dp(i-3) + a[0][i-1] + a[0][i-2]);
}
vis[i] = 1;
return d[i];
}
int main()
{
int T, n;
scanf("%d", &T);
while(T--)
{
memset(a, 0, sizeof(a));
scanf("%d", &n);
for(int i=0; i<n; i++)
scanf("%d", &a[0][i]);
for(int i=0; i<n; i++)
scanf("%d", &a[1][i]);
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
int t;
if(n==3)
{
t = max(a[0][2]+a[0][1]+a[1][0], a[1][2]+a[1][1]+a[1][0]);
}
else if(n==2)
{
t = max(a[0][1]+a[1][0], a[1][1]+a[1][0]);
}
else if(n == 1)
{
t = a[1][0];
}
else if(n >3)
{
t = max(dp(n-1), dp(n-3) + a[0][n-1] + a[0][n-2]);
t = max(t, dp(n-2) + a[0][n-1]);
}
printf("%d\n", t);
}
return 0;
}
自底向上,通過遞推實現:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 200005
int dp[N], a[2][N];
int main() {
int n, t;
cin >> t;
while (t--) {
cin >> n;
memset(a, 0, sizeof(a));
for (int j = 0; j < 2; j++) for (int i = 1; i <= n; i++) scanf("%d", &a[j][i]);
dp[0] = 0;
for (int i = 1; i <= n + 2; i++) {
dp[i] = dp[i - 1] + a[1][i];
if (i > 3) dp[i] = max(dp[i], dp[i - 3] + a[0][i - 2] + a[0][i - 1] + a[1][i]);
}
printf("%d\n", dp[n + 2]);
}
return 0;
}