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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值