区间DP小结(附经典例题) 转载

发布时间 2023-04-27 17:56:12作者: CRt0729

区间DP

转载自:原博客

一、定义

​ 区间DP是线性动态规划的扩展,适用场景为每段区间的最优解可以通过更小区间的最优解得到。所以我们一般的解题思路都是先在小区间得到最优解,然后总结出递推公式,利用小区间的最优解求大区间的最优解。

二、实现伪代码

//mst(dp,0) 初始化dp数组
for(int i=1;i<=n;i++)
{
    dp[i][i]=初始值
}
for(int len=2;len<=n;len++)  //枚举区间长度
for(int i=1;i<=n;i++)        //枚举起点
{
    int j=i+len-1;           //区间终点
    if(j>n) break;           //越界结束
    for(int k=i;k<j;k++)     //枚举分割点,构造状态转移方程
    {
        dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
    }
}

三、经典例题
1. 石子合并问题
1.1 石子合并(一)
石子合并(一)
Time Limit: 1000 MS Memory Limit: 65535 K
Problem Description

有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

Input
​有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0<n<200)个数,分别表示这n堆石子的数目,用空格隔开
Output

输出总代价的最小值,占单独的一行

Examples

Input

3

1 2 3

7

13 7 8 16 21 4 18

Output

9

239

【题目链接】link

【思路】

我们dp[i][j]来表示合并第i堆到第j堆石子的最小代价。

那么状态转移方程为

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);

其中w[i][j]表示把(i,j)和(k+1,j)这两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询并提高效率,我们可以利用前缀和预处理,用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1]。

#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
 
typedef long long ll;
const int maxn = 205;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;
 
int n,x;
int sum[maxn];
int dp[maxn][maxn];
 
int main()
{
    while(~scanf("%d",&n))
    {
        sum[0]=0;
        mst(dp,0x3f);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            sum[i]=sum[i-1]+x;
            dp[i][i]=0;
        }
        for(int len=2;len<=n;len++)
        for(int i=1;i<=n;i++)
        {
            int j=i+len-1;
            if(j>n) continue;
            for(int k=i;k<j;k++)
            {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

【拓展】平行四边形优化

上面的代码运行时间在240ms左右,通过这题完全没问题,但我们还可以考虑优化。

由于状态转移时是三重循环的,我们想能否把其中一层优化呢?尤其是枚举分割点的那个,显然我们用了大量的时间去寻找这个最优分割点,所以我们考虑把这个点找到后保存下来

用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】。(这个时候小区间s[i][j-1]和s[i+1][j]的值已经求出来了,然后通过这个循环又可以得到s[i][j]的值)。

关于平行四边形优化的证明可以参考这篇博客: link

// 32ms
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
 
typedef long long ll;
const int maxn = 205;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;
 
int n,x;
int sum[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
 
int main()
{
    while(~scanf("%d",&n))
    {
        sum[0]=0;
        mst(dp,0x3f);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            sum[i]=sum[i-1]+x;
            dp[i][i]=0;
            s[i][i]=i;
        }
        for(int len=2;len<=n;len++)
        for(int i=1;i<=n;i++)
        {
            int j=i+len-1;
            if(j>n) continue;
            for(int k=s[i][j-1];k<=s[i+1][j];k++)
            {
                if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<dp[i][j])
                {
                    dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
                    s[i][j]=k;
                }
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

1.2 Monkey Party
Monkey Party
Time Limit: 2000 MS Memory Limit: 65535 K
Problem Description
Far away from our world, there is a banana forest. And many lovely monkeys livethere. One day, SDH(Song Da Hou), who is the king of banana forest, decides tohold a big party to celebrate Crazy Bananas Day. But the little monkeys don’tknow each other, so as the king, SDH must do something.
Now there are n monkeys sitting in a circle, and each monkey has a makingfriends time. Also, each monkey has two neighbor. SDH wants to introduce themto each other, and the rules are:
1.every time, he can only introduce one monkey and one of this monkey’sneighbor.
2.if he introduce A and B, then every monkey A already knows will know everymonkey B already knows, and the total time for this introducing is the sum ofthe making friends time of all the monkeys A and B already knows;
3.each little monkey knows himself;
In order to begin the party and eat bananas as soon as possible, SDH want toknow the mininal time he needs on introducing.

Input

There is several test cases. In each case, the first line is n(1 ≤ n ≤ 1000), whichis the number of monkeys. The next line contains n positive integers(less than1000), means the making friends time(in order, the first one and the last oneare neighbors). The input is end of file.

Output

For each case, you should print a line giving the mininal time SDH needs on introducing.

Examples

Input

8

5 2 4 7 6 1 3 9

Output

105

【题目链接】link

【题意】

问题转化后其实就是环形石子合并,即现在有围成一圈的若干堆石子,其他条件跟前面那题相同,问合并所需最小代价——上一题的升级版

【思路】

我们需要做的是尽量向简单的问题转化,可以把前n-1堆石子一个个移到第n个后面,那样环就变成了线,即现在有2*n-1堆石子需要合并,我们只要求下面的式子即可。求法与上面那题完全一样。

min(dp[s][s+n-1]),s∈ \in∈[1,n]
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
 
typedef long long ll;
const int maxn = 1005*2;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
 
int a[maxn];
int sum[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
 
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        sum[0]=0;
        mst(dp,0x3f);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
            s[i][i]=i;
            dp[i][i]=0;
        }
        for(int i=1;i<n;i++)
        {
            sum[i+n]=sum[i+n-1]+a[i];
            s[i+n][i+n]=i+n;
            dp[i+n][i+n]=0;
        }
        for(int len=2;len<=n;len++)
        for(int i=1;i<=2*n-1;i++)
        {
            int j=i+len-1;
            if(j>2*n-1) break;
            for(int k=s[i][j-1];k<=s[i+1][j];k++)
            {
                if(dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
                {
                    dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
                    s[i][j]=k;
                }
            }
        }
        int ans=INF;
        for(int i=1;i<=n;i++)
        {
            ans=min(ans,dp[i][i+n-1]);
        }
        printf("%d\n",ans);
    }
    return 0;
}