石子合并NOI1995--区间DP

题目:
在一个操场上一排地摆放着N堆石子。现要将石子有次序的合并成一堆。规定每次只选相邻的2堆石子合成新的一堆,并将新的一堆石子数记为该次合并的得分。
编程任务:
设计一个程序,计算出将N堆石子合并成一堆的最小得分。
1≤N≤100,0≤ai≤200。
扩展题目:环形结构上的动态规划问题
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N堆石子合并成 1堆的最小得分和最大得分。

输入格式
数据的第 1行是正整数 N,表示有 N 堆石子。
第 2行有 N 个整数,第 i 个整数 ai​ 表示第 i 堆石子的个数。
输出格式
1 行为最小得分
输入样例
7
13 7 8 16 21 4 18
输出样例
239

我们介绍的线性DP一般从初态开始,沿着阶段的扩张向某个方向递推,直至计算出目标状态。区间DP也属于线性DP中的一种,它以“区间长度”作为DP的“阶段”,使用两个坐标(区间的左、右端点)描述每个维度。在区间DP中,一个状态由若干个比它更小且包含于它的区间所代表的状态转移而来,因此区间DP的决策往往就是划分区间的方法。区间DP的初态一般就由长度为1的“元区间”构成。

这种向下划分、再向上递推的模式与某些树形结构,例如线段树,有很大的相似之处。我们把区间DP作为线性DP中一类重要的分支单独进行讲解,将使读者更容易理解以后树形DP的内容。同时,借助区间DP这种与树形相关的结构,我们也将提及记忆化搜索——其本质是动态规划的递归实现方法。

算法思想:

如果有三堆:4,5,6。
第一种方案:文字解释:4和5合并成一堆9,然后9再和6合并成15。花费就是4 + 5 + 9 + 6 = 24。 4 + 5是合并前两堆的花费,在合并后面两堆得时候需要把这个花费算进去。

第二种方案:文字解释:5和6合并成一堆11,然后11再和4合并成15。花费就是5 + 6 + 11 + 4 = 26。同上面的花费解释。
可以明显看出,当选择合并的顺序不同时,所产生的花费是不同的。

使用DP算法来解析这个问题:
我们以4 + 5 + 9 + 6 = 24这个方案来解释DP的思想。我们从数字上来进行解释(4 + 5)表示的是合并第1,2时花费的费用、9表示第1、2合并之后那堆石子的数目,6是第三堆石子的

我们把这个数目扩展一下到 4 堆:4,5,6,7(我们这里没有使用最优解) 那么算式就成了数目。
算式加起来4+5+9+6+15+7=46

46并不是最优解,最优解应该是4+5+6+7+9+13=44。数字不同但是原理是一样的。最优解怎么产生呢?4堆可以是1+3 2+2 3+1三种合并方式,枚举就可以。

从上面看出前三堆所构成的花费是由 三部分构成,1、第一二合并的花费;2、第一二合并的个数和;3:第三堆本身个数

通过上面的理解:
F[ i ][ j ]表示合并第i堆到第j堆花费的最小费用,转移方程为
F[ i ][ j ]=min{F[ i ][ j ],F[ i ][ k ]+F[k+1][ j ]+sum[j]-sum[i-1]}
其中的sum[j]-sum[i-1]表示从i堆到第j堆石子的数目。
F[ i ][ k ]表示从 i 堆到k堆合并石子的最小花费
F[k+1][ j ]表示从k+1堆到 j 堆合并的最小花费
如果把这F[ i ][ k ],F[k+1][ j ]这两堆合并起来用的最小费用就是
F[ i ][ k ]+F[k+1][ j ]以前分别合并这两堆用的费用,
sum[j]-sum[i-1]表示把F[ i ][ k ]、F[k+1][ j ]这两堆再合并成一堆产生的费用(当然产生的费用是这两堆石子数的总和,用前缀和轻松求出)

完整代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[310][310],a[310],sum[310],n;
int main()
{
	cin>>n;
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		f[i][i]=0;
	}
	for(int len=1;len<n;len++)//枚举区间长度
		for(int i=1;i<=n-len;i++)//枚举区间起点
		{
			int j=i+len;//有区间起点和区间长度,算出区间终点
			for(int k=i;k<j;k++)
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
		}
	cout<<f[1][n]<<endl;
}

通过上面例子的学习,我们发现解决区间动规问题,都是用循环嵌套枚举区间长度和起点,再在区间中间枚举区间分割点,用不同的小区间组成这个区间选最优值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值