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

Posted jyssh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间DP小结(附经典例题) 转载相关的知识,希望对你有一定的参考价值。

区间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;

 

区间DP

定义:区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。

动态转移方程一般为dp[i][j]=opt(dp[i][k]+dp[k+1][j]+cost[i][j])

经典例题:取石子问题
很容易根据动态转移方程得出O(n^3)的解法,但是也可以通过四边形不等式优化到O(n^2)
四边形不等式:如果对于任意的a<b<c<d,有m[a,c]+m[b,d]<=m[a,d]+a[b,c],那么m[i,j]满足四边形不等式
对于这个问题,令m[i,j]为取[i,j]石子的最小代价,s[i,j]为取得m[i,j]对应的k值。
首先可以通过数学归纳法证明m[i,j]满足四边形不等式,此处略去。
满足四边形不等式之后就可以证明s[i,j-1]≤s[i,j]≤s[i+1,j],从而可以缩小迭代范围。

百度百科上对s[i,j-1]≤s[i,j]≤s[i+1,j]的证明:
设mk[i,j]=m[i,k]+m[k,j],s[i,j]=d
对于任意k<d,有mk[i,j]≥md[i,j](这里以m[i,j]=minm[i,k]+m[k,j]为例,max的类似),接下来只要证明
mk[i+1,j]≥md[i+1,j],那么只有当s[i+1,j]≥s[i,j]时才有可能有mk[i+1,j]≥md[i+1,j]
(mk[i+1,j]-md[i+1,j])-(mk[i,j]-md[i,j])
=(mk[i+1,j]+md[i,j])-(md[i+1,j]+mk[i,j])
=(m[i+1,k]+m[k,j]+m[i,d]+m[d,j])-(m[i+1,d]+m[d,j]+m[i,k]+m[k,j])
=(m[i+1,k]+m[i,d])-(m[i+1,d]+m[i,k])
∵m满足四边形不等式,∴对于i<i+1≤k<d有m[i+1,k]+m[i,d]≥m[i+1,d]+m[i,k]
∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0
∴s[i,j]≤s[i+1,j],同理可证s[i,j-1]≤s[i,j]
证毕

#include<bits/stdc++.h>
using namespace std;

const int N=205;
const int INF=0x7fffffff;
int n;
int a[N],sum[N],dp[N][N],s[N][N];

int main()
    while(~scanf("%d",&n))
        sum[0]=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
        
        for(int i=1;i<=n;i++) dp[i][i]=0,s[i][i]=i;
        for(int r=1;r<n;r++)
            for(int i=1;i<n;i++)
                int j=i+r;
                if(j>n) break;
                dp[i][j]=INF;
                for(int k=s[i][j-1];k<=s[i+1][j];k++)
                    if(dp[i][j]>dp[i][k]+dp[k+1][j])
                        dp[i][j]=dp[i][k]+dp[k+1][j];
                        s[i][j]=k;
                    
                
                dp[i][j]+=sum[j]-sum[i-1];
            
        
        printf("%d\n",dp[1][n]);
    
    return 0;
   

 

以上是关于区间DP小结(附经典例题) 转载的主要内容,如果未能解决你的问题,请参考以下文章

区间DP小结

区间DP

转载+删改:算法讲解之Dynamic Programing —— 区间DP [变形:环形DP]

区间DP

区间DP

区间型DP