【动态规划】(三)区间DP

动态规划之区间DP

一、区间DP

区间 D P DP DP 是指在一段区间上进行的一系列动态规划。

对于区间 D P DP DP 这一类问题,我们需要计算区间 [ 1 , n ] [1,n] [1,n] 的答案,通常用一个二维数组 d p dp dp 表示,其中 d p [ x ] [ y ] dp[x][y] dp[x][y] 表示区间 [ x , y ] [x,y] [x,y]

有些题目, d p [ l ] [ r ] dp[l][r] dp[l][r] d p [ l ] [ r − 1 ] dp[l][r−1] dp[l][r1] d p [ l + 1 ] [ r ] dp[l+1][r] dp[l+1][r] 推得;也有些题目,我们需要枚举区间 [ l , r ] [l,r] [l,r] 内的中间点,由两个子问题合并得到,也可以说 d p [ l ] [ r ] dp[l][r] dp[l][r] d p [ l ] [ k ] dp[l][k] dp[l][k] d p [ k + 1 ] [ r ] dp[k+1][r] dp[k+1][r] 推得,其中 l ⩽ k ⩽ r l \leqslant k\leqslant r lkr

对于长度为 n n n 的区间 DP,我们可以先计算 [ 1 , 1 ] , [ 2 , 2 ] … [ n , n ] [1,1],[2,2]\dots[n,n] [1,1],[2,2][n,n] 的答案,再计算 [ 1 , 2 ] , [ 2 , 3 ] … [ n − 1 , n ] [1,2],[2,3]\ldots[n-1,n] [1,2],[2,3][n1,n],以此类推,直到得到原问题的答案。

一般来说,区间 D P DP DP 的状态设计有两种:一种是左右端点,用 d p [ l ] [ r ] dp[l][r] dp[l][r] 表示 l ∼ r l\sim r lr 的最优解;一种是左端点和区间长度,用 d p [ l ] [ l e n ] dp[l][len] dp[l][len] 表示 l ∼ l + l e n − 1 l\sim l+len-1 ll+len1 的最优解。

二、区间DP例题

1.洛谷P1880 [NOI1995]石子合并

题目传送门

题目大意

在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。

解析

首先,这种环形问题就应该先破环成链,即将环转换成链。例如,下图这种情况:

4
5
9
4

我们把它转换成下图情况:

4
5
9
4
4
5
9
4

转换好之后,就可以开始转移了。这里,我们就以最大值为例进行说明。

首先,设计状态。我们还是用传统的区间 D P DP DP 的状态设计方式, 用 d p [ l ] [ r ] dp[l][r] dp[l][r] 表示 l ∼ r l\sim r lr 的最大分数。自然而然,我们就可以轻轻松松地得出 d p [ i ] [ i ] = a [ i ] dp[i][i]=a[i] dp[i][i]=a[i] 。同时,最终的答案就是 m a x 1 ⩽ i ⩽ n   d p [ i ] [ i + n − 1 ] max_{1\leqslant i\leqslant n}\,dp[i][i+n-1] max1indp[i][i+n1]

其次,设计状态转移方程。我们想,要合并 l ∼ r l\sim r lr 的所有石子,就可以把它分成两个部分,把两个部分分别合成一堆。我们假设要合并的是 [ l , k ] [l,k] [l,k] [ k + 1 , r ] [k+1,r] [k+1,r] 两堆。那么,首先得到这两堆前,必须要用 d p [ l ] [ k ] dp[l][k] dp[l][k] d p [ k + 1 ] [ r ] dp[k+1][r] dp[k+1][r] 的代价。其次,合并在一起,还要花 a [ l ] + a [ l + 1 ] + ⋯ + a [ r ] a[l]+a[l+1]+\dots+a[r] a[l]+a[l+1]++a[r] 的力气。所以,我们可以得到如下转移方程: d p [ l ] [ r ] = m a x l ⩽ k < r ( d p [ l ] [ k ] + d p [ k + 1 ] [ r ] ) + a [ l ] + a [ l + 1 ] + ⋯ + a [ r ] dp[l][r]=max_{l\leqslant k < r}(dp[l][k]+dp[k+1][r])+a[l]+a[l+1]+\dots+a[r] dp[l][r]=maxlk<r(dp[l][k]+dp[k+1][r])+a[l]+a[l+1]++a[r]。其中, a [ l ] + a [ l + 1 ] + ⋯ + a [ r ] a[l]+a[l+1]+\dots+a[r] a[l]+a[l+1]++a[r] 可以用前缀和优化。

现在,这道题基本就完成了,上代码!

代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int s = 0, w = 1;
	char ch = getchar();
	for(; ch < '0' || ch > '9'; w *= (ch == '-') ? -1 : 1, ch = getchar());
	for(; ch >= '0' && ch <= '9'; s = 10 * s + ch - '0', ch = getchar());
	return s * w;
}
const int MAXN = 205;
int n, pre[MAXN], a[MAXN], dp[MAXN][MAXN];	//dp[i][j] => i~j
signed main(){
	n = read();
	for(int i = 1; i <= n; i++){
		a[i] = read();
		a[i + n] = a[i];
	}
	for(int i = 1; i <= 2 * n; i++){
		pre[i] = pre[i - 1] + a[i];
	}
	memset(dp, 0x3f3f3f3f, sizeof dp);
	for(int i = 1; i <= 2 * n; i++){
		dp[i][i] = 0;
	}
	for(int l = 2; l <= 2 * n; l++){
		for(int i = 1; i + l - 1 <= 2 * n; i++){
			int j = i + l - 1;
			for(int k = i; k < j; k++){
				dp[i][j] = min(dp[i][k] + dp[k + 1][j], dp[i][j]);
			}
			dp[i][j] += pre[j] - pre[i - 1];
		}
	}
	int ans_min = 0x3f3f3f3f;
	for(int i = 1; i <= n; i++){
		ans_min = min(ans_min, max(0ll, dp[i][i + n - 1]));
	}
	cout << ans_min << endl;
	memset(dp, 0, sizeof dp);
	for(int l = 2; l <= 2 * n; l++){
		for(int i = 1; i + l - 1 <= 2 * n; i++){
			int j = i + l - 1;
			for(int k = i; k < j; k++){
				dp[i][j] = max(dp[i][k] + dp[k + 1][j], dp[i][j]);
			}
			dp[i][j] = dp[i][j] + pre[j] - pre[i - 1];
		}
	}
	int ans_max = 0;
	for(int i = 1; i <= n; i++){
		ans_max = max(ans_max, dp[i][i + n - 1]);
	}
	cout << ans_max << endl;
	return 0;
}

2.洛谷P4170 [CQOI2007]涂色

题目传送门

题目大意

假设你有一条长度为 5 5 5 的木板,初始时没有涂过任何颜色。你希望把它的 5 5 5 个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为 5 5 5 的字符串表示这个目标: RGBGR \texttt{RGBGR} RGBGR

每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木板涂成 RRRRR \texttt{RRRRR} RRRRR,第二次涂成 RGGGR \texttt{RGGGR} RGGGR,第三次涂成 RGBGR \texttt{RGBGR} RGBGR,达到目标。

用尽量少的涂色次数达到目标。

解析

这道题的转移方法与上一题有所不同。我们要想的是: d p [ l ] [ r ] dp[l][r] dp[l][r] 会怎么得来?假如两端相等时, d p [ i ] [ j ] dp[i][j] dp[i][j] 的取值应该是 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j] d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] 中的最小值。两段相等的话,还有一种染色方法,就是 d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j1] ,相当于两端都染,这时, d p [ i ] [ j ] dp[i][j] dp[i][j] 还要更新一下最小值。假如两端不相等,那就跟上一题一样,枚举中间端点进行转移。

代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;
const int inf = 0x3f3f3f3f;
int dp[55][55];
int main() {
    string s;
    cin >> s;
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 0; i < s.size(); i++){
        dp[i][i] = 1;
    }
    for(int l = 2; l <= s.size(); l++){
        for(int i = 0; i < s.size() - l + 1; i++){
            int j = i + l - 1;
            if(s[i] == s[j]){
                if(l == 2){
                    dp[i][j] = 1;
                } else {
                    dp[i][j] = min(min(dp[i + 1][j], dp[i][j - 1]), dp[i + 1][j - 1] + 1);
                }
            } else {
                for(int k = i; k < j; k++){
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
                }
            }
        }
    }
    cout << dp[0][s.size() - 1] << endl;
    return 0;
}

三、总结与反思

区 间 D P { 概 念 − 在 一 段 区 间 上 进 行 的 一 系 列 动 态 规 划 状 态 设 计 { ① 左 右 端 点 : d p [ l ] [ r ] 表 示 l ∼ r 的 最 优 解 ② 左 端 点 和 长 度 : d p [ l ] [ l e n ] ( 少 见 ) 状 态 转 移 { ① 枚 举 中 间 端 点 k , 通 过 d p [ l ] [ k ] 与 d p [ k + 1 ] [ r ] 转 移 ② 通 过 d p [ l ] [ r − 1 ] 与 d p [ l + 1 ] [ r ] 转 移 例 题 分 析 { 1. 石 子 合 并 , 转 移 方 法 : ① 2. 涂 色 , 转 移 方 法 : ① 和 ② 区间DP\begin{cases}概念-在一段区间上进行的一系列动态规划\\状态设计\begin{cases} ①左右端点:dp[l][r]表示l\sim r的最优解\\②左端点和长度:dp[l][len](少见)\end{cases}\\状态转移\begin{cases} ①枚举中间端点k,通过dp[l][k]与dp[k+1][r]转移\\②通过dp[l][r-1]与dp[l+1][r]转移\end{cases}\\例题分析\begin{cases} 1.石子合并,转移方法:①\\2.涂色,转移方法:①和② \end{cases}\\ \end{cases} DP{dp[l][r]lrdp[l][len]{kdp[l][k]dp[k+1][r]dp[l][r1]dp[l+1][r]{1.2.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值