区间DP入门

所为区间DP,主要是把一个大区间拆分成几个小区间,先求小区间的最优值,然后合并起来求大区间的最优值。

区间DP最关键的就是满足最优子结构以及无后效性!!

例如像是石子合并和括号匹配这两类比较经典的模型。

一般的区间dp写法是:

    for(int len=2;len<=n;len++)    //枚举区间长度 
    {
        for(int i=1;i<=(n<<1)-len+1;i++)    //区间的左端点 
        {
            int j=i+len-1;
            for(int s=i;s<j;s++)
            {
                //大区间与小区间的关系;
            }
        }
    }

转移方程的推理:

首先,要计算合并的最大值、最小值,既然是动态规划,我们需要洞悉其中一些关联且确定的状态。

以下以最大值为例。

既然是最大值,那么求得的结果是否满足每一区间都是该区间所能达得到的的最大值?

显然是这样的。反证法:倘若有一个区间不是,那么换做该区间取得最大值的方案,最终结果将比原得分大。显然必定满足任意区间得分一定是该区间内的最大值。

这样我们可以定义状态f[i][j],表示i到j合并后的最大得分。其中1<=i<=j<=N。

既然这样,我们就需要将这一圈石子分割。很显然,我们需要枚举一个k,来作为这一圈石子的分割线。

这样我们就能得到状态转移方程:

$f[i][j] = max(f[i][k] + f[k+1][j] + d(i,j))$ 其中,1<=i<=<=k<j<=N。

d(i,j)表示从i到j石子个数的和,也就是合并的代价。

需要确定首尾指针,和枚举中间的断点,时间复杂度O(n3),虽然可以进一步利用什么四边形优化,在这里不做过探讨。

 

对于最简单的石子合并(成链状)来说,就是一个模板题,答案自然是dp[1][n]。

而对于呈环状的的来说则需要将数据复制一遍,答案为$max(dp[ i =(1-n) ][ i+n-1 ])$

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
#include <map>
using namespace std;
#define LL long long
#define mod int(1e9+7)
#define wlz 1234567890
int n,ans1,ans2;
int a[211],sum[211],f1[211][211],f2[211][211];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i+n]=a[i];
    }
    for(int i=1;i<=n*2;i++)
    {
        sum[i]=sum[i-1]+a[i];
        f1[i][i]=f2[i][i]=0;
    }
    for(int len=2;len<=n;len++) 
    {
        for(int i=1;i<=(n<<1)-len+1;i++) 
        {
            int j=i+len-1;
            f1[i][j]=wlz;
            for(int s=i;s<j;s++)
            {
                f1[i][j]=min(f1[i][j],f1[i][s]+f1[s+1][j]);
                f2[i][j]=max(f2[i][j],f2[i][s]+f2[s+1][j]);
            }
            f1[i][j]+=(sum[j]-sum[i-1]);
            f2[i][j]+=(sum[j]-sum[i-1]);
        }
    }
    ans1=wlz;
    for(int i=1;i<=n;i++)
    {
        ans1=min(ans1,f1[i][i+n-1]);
        ans2=max(ans2,f2[i][i+n-1]);
    }
    printf("%d\n%d",ans1,ans2);
}
放上环状代码

 

对于括号匹配这一类。

给一个括号组成的字符串,问最多能匹配多少个括号 

我们可以把[i,j]区间的字符当成由[i+1,j]在前面加个字符或[i,j-1]在后面加一个字符得来的

这里我们只考虑[i,j]由[i+1,j]在前面加一个字符的情况 
如果a[i+1]到a[j]没有和a[i]匹配的,那么dp[i][j] = dp[i+1][j] 
如果a[k]和a[i]匹配(i < k <= j),那么dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2); 
比如:[xxxxx]yyyyy通过括号分成两个子串

第二种模型就是根据匹配信息把区间划分成[i+1,k-1]和[k+1,j]

while (gets(a+1))
{
    if(a[1] == 'e') break;
    memset(dp, 0, sizeof(dp));
    int n = strlen(a+1);
    for (int len = 2; len <= n; len++)
    {
        for(int i = 1, j = len; j <= n; i++, j++)
        {
            dp[i][j] = dp[i+1][j];
            for (int k = i; k <= j; k++)
                if((a[i]=='('&&a[k]==')') || (a[i]=='['&&a[k]==']'))
                    dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2);
        }
    }
    printf("%d\n",dp[1][n]);
}
代码还可以这样写

 

还有一类只需要枚举左右边界,比较简单,就不展开讲了。

posted @ 2018-08-22 10:00  Manjusaka丶梦寒  阅读(475)  评论(0编辑  收藏  举报