区间类动态规划(dp)

一、问题引入

	给定长为n的序列a[i],每次可以将连续一段回文序列消去,消去后左右两边会接到一起,
求最少消几次能消完整个序列,n≤500。
	与线性模型不同,这里消去的顺序是任意的,且消完后左右会接起来。但我们发现,不管
消去的顺序是什么,每个时刻被消去的位置总是一段连续区间。 
	考虑消去区间[i,j]]时,若a[i],a[j]不在一起消去,则总能找到一个分界点k,使得我
们能先消完[i,k]再去消[k+1,j]。注意这里是只考虑消[i,j],因此不考虑与外面一起消去。 
	若a[i],a[j]在一起消去,则它们只要接在[i+1,j-1]这段区间中最后一次消除时那个回
文序列的左右即可。
	f[i][j]表示消去区间[i,j]需要的最少次数.
	容易发现这样问题就具有了最优子问题结构且状态满足无后效性。
	f[i][j]=min{f[i][k]+f[k+1][j]|i<=k<j}
	若a[i]=a[j],则还有f[i][j]=min{f[i][j],f[i+1][j-1]}
	这里实际上是以区间长度为阶段的,这种DP我们通常称为区间DP,
	区间DP的解法较为固定,即枚举区间长度,再枚举左端点,之后枚举区间的断点进行转移。
一般使用DP时的特征比较明显,就像上述例子,每次可以消一个区间,之后左右两边会合并起来。
有时问题不像上述例子,消去整个区间可以拆分成每次消去一对位置对称的元素,那么转移时
f[i][j]的初值就需要提前计算,赋为一次消去它的值。

二、基本概念

	区间类动态规划是线性动态规划的拓展,它在分阶段划分问题时,与阶段中元素出现的顺序和
由前一阶段的哪些元素合并而来有很大的关系。如状态f[i][j],它表示以已合并的次数为阶段、以
区间的左端点i为状态,它的值取决于第i个元素和第j个元素断开的位置k,即f[i][k]+f[k+1][j]
的值。这一类型的动态规划,阶段特征非常明显,求最优值时需要预先设置阶段内的区间统计值,
还要以动态规划的起始位置来判断。
	区间类动态规划的特点:
	合并:即将两个或多个部分进行整合,当然也可以反过来,也就是对一个问题分解成两个或多
个部分。
	特征:对将问题分解成为两两合并的形式。
	求解:对整个问题设最优值、枚举合并点,将问题分解成左右两个部分,最后合并左右两个部
分的最优值得到原问题的最优值。有点类似分治算法的解题思想。
	区间类动态规划的典型应用有:石子合并、能量项链、凸多边形的划分等。

三、例题

石子合并

题目描述
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:

选择一种合并石子的方案,使得做 n-1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
输入第一行一个整数 n,表示有 n 堆石子。

第二行 n 个整数,表示每堆石子的数量。

输出格式
输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

样例
Input Output
4
4 5 9 4
43
54
数据范围与提示
对于 100%的数据,有 1≤n≤200。

分析:
第一次看到这道题,很多选手会采用尽可能逼近目标的贪心法做逐次合并。如样例,从第一堆开始,沿顺时针方向排成一个环,要计算得分最小的,第一次取相邻得分最小的4,4两堆合并,得分为8分.合并后剩下3堆,每堆的分数为8,9,5;再选相邻得分最小的5,8合并,得13分,最后的9和13两堆合并,得22分,总共分数为43,与样例输出一样,好像这种贪心策略是对的。其实不然,这种策略存在反例。例如,6堆石子,从最上面一堆顺时针数起,第一堆石子数为3,第二堆石子数为4,第三堆石子数为6,第四堆石子数为5,第五堆石子数为4,第六堆石子数为2,采用贪心兼略合并时,合并过程如图5-1-1所示
在这里插入图片描述
但是经过反复推敲,可以得到一种总得分更小的合并方案,合并过程如图5-1-2
在这里插入图片描述

	正解:若最初的第l堆石子和第r堆石子被合并成一堆,则说明l~r之间的每堆石子也已经被
合并,这样l和r才有可能相邻。因此,在任意时刻,任意一堆石子均可以用一个闭区间[l,r]来
描述,表示这堆石子是由最初的第l~r堆石子合并而成的,其重量为∑ai(l≤i≤r)。另外,一定存
在一个整数k(l≤k<r),在这堆石子形成之前,先有第l~k堆石子(闭区间[l,k])被合并成一堆,
第k+1~r堆石子(闭区间[k+1,r])被合并成一堆,然后这两堆石子才合并成[l,r]。
	对应到动态规划中,就意味着两个长度较小的区间上的信息向一个更长的区间发生了转移,
划分点k就是转移的决策。自然地,应该把区间长度len作为DP的阶段。不过,区间长度可以由
左端点和右端点表示,即len=r-l+1。本着动态规划“选择最小的能覆盖状态空间的维度集合”
的思想,我们可以只用左、右端点表示DP的状态。
	设sum[i]表示从第l堆到第i堆石子数总和sum[i]=a[1]+a[2]+a[i]。
	Fmax[i][j]表示从第i堆石子合并到第j堆石子的最大的得分。
	Fmin[i][j]表示从第i堆石子合并到第j堆石子的最小的得分。
则状态转移方程为:
	Fmax[i][j]=max{Fmax[i][k]+Fmax[k+1][j]+sum[j]-sum[i-1]}(i≤k≤j-1).
	Fmin[i][j]=min(Fmin[i][k]+Fmin[k+1][j]+sum[j]-sum[i-1])(i≤k≤j-1)
  初始条件:Fmax[i][i]=0,Fmin[i][i]=INF。
  时间复杂度为O()

AC代码:

#include <iostream>
using namespace std;
const int maxn=205,inf=0x7fffffff/2;
int f1[maxn][maxn],f2[maxn][maxn],s[maxn][maxn]={0};
int a[maxn],sum[maxn]={0},n,ans1,ans2;
void init(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i+n]=a[i];
	}
	for(int i=1;i<=n*2;i++){
		sum[i]=sum[i-1]+a[i];
		f2[i][i]=0;
		f1[i][i]=0;
	}
}
void dp(){
	for(int l=2;l<=n;l++){
		for(int i=1;i<=2*n-l+1;i++){
			int j=i+l-1;
			f1[i][j]=inf;
			f2[i][j]=0;
			for(int k=i;k<j;k++){
				f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j]);
				f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j]);
			}
			f1[i][j]+=sum[j]-sum[i-1];
			f2[i][j]+=sum[j]-sum[i-1];
		}	
	}
	ans1=inf,ans2=0;
	for(int i=1;i<=n;i++)
		ans1=min(ans1,f1[i][i+n-1]);
	for(int i=1;i<=n;i++)
		ans2=max(ans2,f2[i][i+n-1]);
}
int main(){
	init();
	dp();
	printf("%d\n%d\n",ans1,ans2);
	return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值