1. 程式人生 > >動態規劃學習系列——區間DP(二)

動態規劃學習系列——區間DP(二)

上一篇我們看了區間型DP的一道經典入門題——石子歸併,這一次同樣是類似的一道題——石子歸併2
題目連結:wikioi 2102
題幹不同之處在於,現在我們的石子不是排成一列了,而是圍成一個環,我們要怎麼把問題轉化成普通的石子歸併呢?
其實這是一種挺常見的演算法技巧——變環為列
方法:長度為len的環 —> 長度為2*len的列
為什麼這樣變換是成立的呢?因為每一種擷取順序都可以在變換後的列出現。
通過這樣一個方法,把一個環形DP變成了普通的DP了,這樣就是普通的石子歸併了,狀態轉移方程是:dp[i][j]=min(dp[i][j] , dp[i][k] + dp[k+1][j] + sum[i:j])

關鍵程式碼:
首先是預處理(變環為列……):

for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
    a[i+n]=a[i];

接下來是記錄連續和sum陣列:

for(int i=1;i<=2*n;i++)
    sum[i]=sum[i-1]+a[i];

DP過程:

for(int len=2;len<=n+1;len++)
{
    for(int i=1;i<=2*n-len+1;i++)
    {
        int j=i+len
-1; for(int k=i;k<j;k++) { dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]); dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]); } } }

dp1陣列代表最大代價,dp2陣列代表最小代價(初始化要注意哦~)

完整的AC程式碼:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std; int n,a[205],sum[205]; int dp1[205][205],dp2[205][205]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) a[i+n]=a[i]; memset(dp1,0,sizeof(dp1)); memset(dp2,INF,sizeof(dp2)); for(int i=1;i<=2*n;i++) dp2[i][i]=0; for(int i=1;i<=2*n;i++) sum[i]=sum[i-1]+a[i]; for(int len=2;len<=n+1;len++) { for(int i=1;i<=2*n-len+1;i++) { int j=i+len-1; for(int k=i;k<j;k++) { dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]); dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]); } } } int minx=INF,maxx=0; for(int i=1;i<=n;i++) { minx=min(minx,dp2[i][i+n-1]); maxx=max(maxx,dp1[i][i+n-1]); } printf("%d\n%d\n",minx,maxx); return 0; }

其實這不僅僅是區間DP的例題,而且是環形DP的例題,環形DP的基本思路和解法都是把環轉化成普通的列,這種技巧不僅適用於DP,而且適用於各種字串匹配的題。