动态规划(DP)---- 区间DP

文章介绍了区间动态规划在解决石子合并问题中的应用,通过分析前缀和和状态转移方程,给出了两种方法计算将n堆石子合并成一堆的最小花费,强调了避免重复运算的重要性。
摘要由CSDN通过智能技术生成

前面章节已经讲到背包问题,硬币问题,这期继续给大家带来DP问题中的区间DP。

区间DP其实指的是在小区间内部进行DP得到小区间的最优解,将小区间逐渐合并来得到大区间的最优解。在解决区间DP问题我们通常会利用模版利用for循环枚举全部可能区间,然后分析状态转移方程

给大家看一道经典例题:

有n堆石子排成一排,每堆石子有一定数量,将n堆石子合并成一堆,合并的过程只能合并相邻的两堆石子,合并所需的代价为两堆石子的总数,求合并成一堆的最小花费。

输入样例:

3

2 4 5

输出样例:

17

咱们先来分析样例,2 4 5 ,先合并 2 + 4 = 6,然后6 + 5 = 11,所以总花费为6 + 11 = 17。

首先研究dp数组的含义。我们分析题干,将两个相邻的石子合并,先从最末端开始,最后的操作肯定是剩下两堆石子进行相加,所以这里咱们设置一个k来枚举将一堆拆成两堆的所有情况,咱们设dp[i][j],dp数组的含义为从第i堆石子到第j堆石子的最小花费。(由整化零)那么分析他的状态转移方程:

1.最开始的dp[i][i]为第i堆石子,没有合并,所以dp[i][i] = 0

2.接下来分析j = i + 1的情况下的两堆合并:首先对于第一堆和第二堆分析->dp[1][2] = dp[1][1] + dp[2][2] + s[2] - s[1].

(这里的是s数组为前缀和代表含义为s[i]是前i个元素的和),1到2堆的最小花费=第一堆的最小花费+第二堆的最小花费+1到2之间的数字和

3.若i与j不相邻,那么咱们就要一堆化两堆:dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j] + s[j] - s[i])

举个例子:1 2 3 4 5 6 7

i->1,j->7,那么想将这一堆拆分两堆,所有的情况是不是从k = 1开始枚举到k = 6,类似于k是刀,要将这一条线割成两条线的所有的方案:

for(int k = i;k < j;k++)
    dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j] + s[j] - s[i];

 那么接下来就是遍历顺序,首先枚举i到j的距离,然后逐渐枚举i并不断刷新j使其可以包括所有可能的情况区间。

for(int len = 1;len <= n;n++)
{
	for(int i = 1;i + len <= n;i++)
	{
		int j = i + len;
		dp[i][j] = 1e9;
		for(int k = i;k < j;k++)
		{
			dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
		}
	}
}

 那么请看完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int n;
int s[N];//前缀和
int dp[N][N];
int main()
{
	cin >> n;
	//计算前缀和
	for(int i = 1;i <= n;i++)
	{
		s[i] += s[i - 1];
	}
	for(int len = 1;len <= n;n++)
	{
		for(int i = 1;i + len <= n;i++)
		{
			int j = i + len;
			dp[i][j] = 1e9;
			for(int k = i;k < j;k++)
			{
				dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
			}
		}
	}
	cout << dp[1][n] << endl;
	return 0;
}

其实在分析过程中,我们可以发现k的枚举会有重复多余的部分,每次运行区间内部都是为了找最优的k值,那么我们可以记录当前的k值,以此来达到避免重复运算。

#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int n;
int s[N];//前缀和
int dp[N][N];
int good[N][N];//存放k的最优值
int main()
{
	cin >> n;
	//计算前缀和
	for(int i = 1;i <= n;i++)
	{
		s[i] += s[i - 1];
	}
	for(int i = 1;i <= n;i++)
	{
		good[i][i] = i;
	}
	for(int len = 1;len <= n;n++)
	{
		for(int i = 1;i + len <= n;i++)
		{
			int j = i + len;
			dp[i][j] = 1e9;
			for(int k = good[i][j - 1];k <= good[i + 1][j];k++)
			{
				dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k + 1][j] + s[j] - s[i]);
				s[i][j] = k;//记录
			}
		}
	}
	cout << dp[1][n] << endl;
	return 0;
}

好了,本期内容就到这里了,感谢收看,记得三连支持哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记得开心一点嘛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值