簡單動態規劃(scauacm 17996 Daily Cool Run)

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

這個數列的的LIS1 5 6 7 8,長度爲5

1 2 7 85 6 7 8同樣爲數列的子上升數列,但它們都不是最長上升子數列。


最優子結構

用動態規劃方法求解最優化問題的第一步是刻畫出最優解的結構。如果一個問題的最優解包含其子問題的最優解,就稱此問題具有最優子結構性質。


重新定義上述問題:

給定一個長度爲N的數列;

Fk爲:以數列第K項結尾的LIS長度

則F1~N中的最大值即爲所求

對於求解Fk,每個F1~k-1都是Fk的子問題。不難證明,以k項結尾的LIS包含着以1k-1項結尾的LIS


需要注意的是,定義最優子結構有個隱含條件,就是子結構的無後效性。從F1_k-1可以求得Fk,也就是每個階段的最優解可以從之前某個階段的最優解直接得到,則稱這個問題具有最優子結構。

而同時,不管之前某個階段的最優解是如何得到的,不管F1~k-1是如何得到的,因爲要求Fk時,只需知道F1~k-1的結果,而並不關心它是如何求出來的。


子問題重疊

重疊的子問題實際上是同一個問題,只是作爲不同問題的子問題出現。通過記錄這些重疊的子問題,避免問題的反覆求解,從而達到以空間換時間的效果。


3、動態規劃的具體實現


題意:

天天酷跑,給一個2*n的格子,初始時在(21)這個點上,每次兩種操作

從(2i)移動到(2i+1

從(2i)跳起,經過(1i+1)(1i+2)跳到(2i+3

當到達(1n)或者(2n)的時候,遊戲結束,每個格子上有一個分數,遊戲總分數就是經過的格子分數之和,求能獲得的分數的最大值


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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章