区间DP详解

前置知识

前缀和:指的是前n个数的和,通常可以用来求区间和
DP:动态规划,本质上仍然是暴力枚举,只不过利用空间换时间的方式,减少了重复的计算;DP需要知道转移方程、初始值、遍历顺序等信息才能正确使用

区间DP的概念

顾名思义就是对区间求DP,如dp[i][j]表示为区间[i,j]所求的值;通常可以将区间内的求解转化为两个部分分开求解,如dp[i][j] = dp[i][k]+dp[k+1][j]+区间的前缀和

模板题:洛谷P1775 石子合并弱化版

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1b1a9d27b33b4ceebcef4e9e2a049106.png

题目解析:从题目的直接感受,要求最小代价,肯定用贪心,想用局部最优推出全局最优;但实际上,贪心未必能次次取到最小代价,贪心只能从表面上的两个最小值来推出局部最小值,实际情况有时是稍微取到较大值,然后在下一次合并取到更小的值,考虑到不管怎样合并,都存在子问题到大问题的过程,如不管哪两堆合并都必然影响到其他堆的合并,所以这道题可以用DP暴力枚举所有情况

还考虑到堆的合并可以抽象为区间内的合并,所以这道题实质是区间DP

解题思路

既然DP就是暴力枚举,必然需要枚举所有的情况,而对于任意区间的合并,实际在最后两堆合并的时候是固定值,即这个区间的和,而区间和可以由前缀和相减得到

状态定义:由于是区间DP,自然是将状态定义为区间值,所以定义dp[i][j]为[i,j]区间的最小合并值

DP数组的转移方程:显然由于是两两堆合并,那么对于dp[i][j]就必然等于[i,j]区间内任意两大堆的最小值合并得到,即dp[i][j] = min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);区间和自然是由前缀和相减得到

DP数组初始值:对于求最小值,显然要将所有值初始为最大值,但对于区间长度为1的格子显然应该初始化为0,毕竟自己与自己合并的代价为0

遍历:
1.由于DP需要从子问题才能到下一个状态,需要枚举区间长度,对于长度为1的区间已经初始化过了,所以区间长度从2开始,知道所有堆的长度n
2.枚举所有区间的左端点,毕竟只有这样才能通过左端点和区间长度知道右端点
3.再枚举分割点k,由于当区间[i,j]较大时也是由子问题的最优解得来,所以仍然需要枚举分割点k来保证取到最优解

C++代码

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

const int N = 310, INF = 0x3f3f3f3f;
ll sum[N], a[N];
ll f1[N][N];

int n;

int main(){
	cin >> n;
	memset(f1, INF, sizeof f1);	//最小 
	for(int i=1; i<=n; ++i){
		cin >> a[i];
		sum[i] = sum[i-1]+a[i];
		f1[i][i] = 0;
	}
	for(int len=2; len<=n; ++len){
		for(int l=1; l+len-1<=n; ++l){
			int r = l+len-1;
			for(int k=l; k<r; ++k){
				f1[l][r] = min(f1[l][r], f1[l][k]+f1[k+1][r]+sum[r]-sum[l-1]);
			}
		}
	}
	cout << f1[1][n];
	return 0;
}

时间复杂度:O(n^3)

总结

1.对于区间DP问题,往往需要通过观察法看出存在合并两个或多个的问题,并且通常有最值问题,这样往往就是八九不离十了

2.题目给的样例可能会诱导你去使用贪心,但实际情况可能更复杂

3.区间DP通常可以套模板,模板也比较好记,算是简单的DP

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值