1. 程式人生 > >2017級演算法模擬上機準備篇(序列DP 初階)

2017級演算法模擬上機準備篇(序列DP 初階)

序列DP 是一類蠻有意思的DP  序列區別於陣列的地方就在於沒有要求連續性

從一道簡單的序列DP開始:

SkyLee炒股票(最大連續子序列和)

這道題其實用DP有點浪費的感覺,其實這道題用簡單的遍歷法也可以,不過實質上還是DP思想的進階。

這道題如何設定DP陣列?我們不妨設定DP[i]為以陣列元素ar[i]結尾的連續序列的最大和.

所以我們很容易得出狀態轉移方程:

dp[i]=max(dp[i-1] + ar[i] , ar[i]);

倘若之前的dp[i-1]再加上當前元素 得到的值更大,那麼最大連續子序列的長度就會延長,如果沒有那麼就可以使其單個元素成為一個連續子序列。

 

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int Maxlen = 1e6 + 10;
long long num[Maxlen];
long long dp[Maxlen];//dp[i]代表的是以i元素結尾的連續子序列的最大值
int main(int argc, char *argv[]) { int n,i,j,k; long long ans; while(~scanf("%d",&n)){ for(i=1;i<=n;i++) scanf("%lld",&num[i]); dp[0]=0; ans=0; for(i=1;i<=n;i++){ dp[i]=max(dp[i-1]+num[i],num[i]); ans=max(ans,dp[i]); } printf("%lld\n",ans); } return 0; }

那如果我們要繼續求解最大連續子序列和的起點和終點的位置呢?

顯然只需要利用dp陣列進行一次搜尋即可。

            if(dp[i]>ans){
                ans=dp[i];
                right=i;
                left=i;
                while(dp[left] > 0 && left >=1)
                    left--; if(left ==0 || dp[left] < 0) left++; }

 

 接下來再來求解一道經典的序列DP 問題 LIS

ModricWang的導彈攔截系統(最長不上升子序列 LIS)

這道題其實就是求解最長不上升子序列長度。

我們首先思考DP陣列的設定:dp[i]代表的是以陣列元素ar[i]為結尾元素的不上升子序列長度。

那轉移方程呢?

for(i=1;i<=n;i++){
        for(j=1;j<i;j++){
            if(ar[j]>=ar[i])
                dp[i]=max(dp[j]+1,dp[i]);
        }
        ans=max(ans,dp[i]);
    }

 

其實到這一步 就可以簡單的總結一下序列DP的解法。

做DP題的第一步就是合理的構造有確定含義 符合最優子結構 的DP陣列

然後是實現狀態轉移方程,其第一步的構思和第二部略有重複。最後實現一下DP陣列的初始化,就可以完成了。

 

序列DP的dp陣列的設定通常具有的含義是 以給定陣列ar[i]結尾的具有某種特殊含義的陣列,然後繼續思考狀態轉移方程即可。

由於序列只有前後的邏輯關係,所以子問題和父問題,可以簡單的理解為前後關係。

同時序列DP的最值問題,通常在遍歷求解DP陣列的同時,更新即可。

最長公共子序列(LCS)

給定兩個序列,求解最長公共子序列的問題。

根據上述設定dp陣列的思路 我們不妨設定 dp[i][j] 其中i為序列1 以下標i結尾 j為序列2 以下標j結尾

狀態轉移方程:

 if(s1[i]==s2[j])
                dp[i][j]=dp[i-1][j-1]+1;
            else
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

最長上升公共子序列(LCIS)

http://acm.hdu.edu.cn/showproblem.php?pid=1423(原題連結)

顯然這道題是基與LCS的,那麼如何求解上升的子問題,我們還是按照序列dp一貫的思路設定dp陣列:

設定 dp[i][j] 其中i為序列1 以下標i結尾 j為序列2 以下標j結尾 顯然這些是不夠的

我們在此之上可以 新增一點條件:我們不妨設定dp[i][j]的公共子序列的末尾元素是dp[j]即:

dp[i][j]表示以a串的前i個整數與b串的前j個整數且以b[j]為結尾構成的LCIS的長度

 

那麼我們來實現狀態轉移方程:

    if(a[i] != b[j])
        dp[i][j]=dp[i-1][j];
    else {
        for(k=1;k<=j-1;k++)
            if(b[j]>b[k])
                dp[i][j]=max(dp[i][j],dp[i-1][k]+1); }

從這道題我們可以總結出來序列DP的一些其他思想,如果存在兩個以上變化的量,我們可以考慮以一個變數為標準,相當於確定了一個變數,這樣思考另一個變數帶來的影響更簡潔。 

中等·Bamboo's Fight with DDLs II (類似LCS的序列DP問題)

dp[i][j]代表的是序列區間是從i到j的最長迴文子序列長度

                if(str[i] == str[j])
                    dp[i][j]=2+dp[i+1][j-1];
                else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);