区间DP学习笔记

动态规划的性质:

最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。

子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。

无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

区间DP

一种运用于区间计算的DP算法

主要思想:

先在小区间取得最优解,然后小区间合并时更新大区间的最优解

基本代码:
// dp[i][j] 表示以i为起点j为终点的区间的答案

for (int i = 1; i <= n; i++) { // 初始化dp数组
	dp[i][i] = value[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] + cost[i]
					}
			}
		}
return dp[1][n]; // 最终答案

例题
例题1 hrbust 1818 石子合并问题–直线版

一条直线上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。

Input
输入有多组测试数据。

每组第一行为n(n<=100),表示有n堆石子,。

二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100)

Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。

Sample Input
3

1 2 3

Sample Output
9 11

思路:
设定 DP[i][j] 为区间从 ij的最优合并解
其状态转移方程为

dp[i][i]=min/max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=105;
int stone[N];
int mdp[N][N];
int madp[N][N];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(mdp,0x3f,sizeof(mdp));
        memset(madp,0,sizeof(madp));
        memset(stone,0,sizeof(stone));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&stone[i]);
            stone[i]+=stone[i-1];
            mdp[i][i]=0;
            madp[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) break;
                for(int k=i;k<j;k++)
                {
                    int mtp=mdp[i][k]+mdp[k+1][j]+stone[j]-stone[i-1];
                    int matp=madp[i][k]+madp[k+1][j]+stone[j]-stone[i-1];
                    mdp[i][j]=min(mdp[i][j],mtp);
                    madp[i][j]=max(madp[i][j],matp);
                }
            }
        }
        printf("%d %d\n",mdp[1][n],madp[1][n]);
    }
    return 0;
}
例题2:hrbust 1819 石子合并问题–圆形版

在圆形操场上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。

Input
输入有多组测试数据。
每组第一行为n(n<=100),表示有n堆石子,。

二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100)

Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。

Sample Input
3
1 2 3

Sample Output
9 11

思路:
先将数组倍增后,使用直线版做法求解即可

代码实现:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=210;
int stone[N];
int sum[N];
int mdp[N][N];
int madp[N][N];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(mdp,0x3f,sizeof(mdp));
        memset(madp,0,sizeof(madp));
        memset(stone,0,sizeof(stone));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&stone[i]);
            sum[i]=sum[i-1]+stone[i];
            mdp[i][i]=0;
            madp[i][i]=0;
        }
        for(int i=n+1;i<=2*n;i++)
        {
            sum[i]=sum[i-1]+stone[i-n];
            mdp[i][i]=0;
            madp[i][i]=0;
        }
        for(int len=2;len<=n;len++)
        {
            for(int i=1;i<=2*n;i++)
            {
                int j=i+len-1;
                if(j>2*n) break;
                for(int k=i;k<j;k++)
                {
                    int mtp=mdp[i][k]+mdp[k+1][j]+sum[j]-sum[i-1];
                    int matp=madp[i][k]+madp[k+1][j]+sum[j]-sum[i-1];
                    mdp[i][j]=min(mdp[i][j],mtp);
                    madp[i][j]=max(madp[i][j],matp);
                }
            }
        }

       int ans1=1e9,ans2=0;
       for(int i=1;i<=n;i++)
       {
           ans1=min(mdp[i][i+n-1],ans1);
           ans2=max(madp[i][i+n-1],ans2);
       }
       printf("%d %d\n",ans1,ans2);
    }
    return 0;
}
例题三:括号匹配

poj 2955 Brackets

We give the following inductive definition of a “regular brackets” sequence:

the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:

(), [], (()), ()[], ()[()]

while the following character sequences are not:

(, ], )(, ([)], ([(]

Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.

Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].

Input

The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.

Output

For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

Sample Input
((()))
()()()
([]])
)[)(
([][][)
end

Sample Output
6
6
4
0
6

思路:
运用DP算法求解,定义dp[i][j] 为区间 ij 中的括号最大匹配数目,若已知区间 i+1j-1 的括号匹配数目,且 i 与 j 的 括号匹配则可推出

dp[i][j]=dp[i+1][j-1]+2;

所以我们只要枚举 i 到 j 的括号数目再通过求区间内的最大括号数目,即可求得 dp[i][j]的最大括号数目

通过枚举 i 和 j 的中间值,即可求得dp[i][j] 的最大值
dp[i][j]=max(dp[i][j],dp[i][m]+dp[m+1][j]) ;

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
const int N=105;
int dp[N][N];
int main()
{
    string c;
    while(cin>>c)
    {
       if(c=="end") break;
       memset(dp,0,sizeof(dp));
       for(int i=1;i<c.size();i++)
       {
           for(int j=0,k=i;k<c.size();k++,j++)
           {
               if((c[j]=='(' && c[k]==')') || (c[j]=='['&&c[k]==']'))
                        dp[j][k]=dp[j+1][k-1]+2;
                for(int m=j;m<k;m++)
                    dp[j][k]=max(dp[j][k],dp[j][m]+dp[m+1][k]);
           }
       }
       printf("%d\n",dp[0][c.size()-1]);
    }
    return 0;
}

四边形不等式优化

四边形不等式的证明:
四边形不等式优化讲解(详解)

当决策代价函数w[i][j] 满足w[i][j]+w[i’][j’]<=w[I;][j]+w[i]
j’ 时,称w满足四边形不等式.当函数w[i][j] 满足w[i’][j]<=w[i][j’] i<=i’<=j<=j’) 时,称w关于区间包含关系单调.如果状态转移方程m为
在这里插入图片描述
且决策代价w满足四边形不等式的单调函数(可以推导出m亦为满足四边形不等式的单调函数),则可利用四边形不等式推出最优决策s的单调函数性,从而减少每个状态的状态数,将算法的时间复杂度由原来的O(n3)降低为O(n2).方法是通过记录子区间的最优决策来减少当前
的决策量

在这里插入图片描述

参考代码

memset(s,0,sizeof s);
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++)
{
    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++)
        {
            int tmp=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
            if (tmp < dp[i][j])
            {
                dp[i][j] = tmp;
                s[i][j] = k;
            }
        }
    }
}
return dp[1][n];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值