动态规划学习系列——区间DP(二)

时间:2022-12-15 22:47:08

上一篇我们看了区间型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,而且适用于各种字符串匹配的题。