P1880 [NOI1995]石子合并 (区间DP)

 

P1880 [NOI1995]石子合并

 

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

 

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

 

输出格式:

 

输出共2行,第1行为最小得分,第2行为最大得分.

 

输入输出样例

输入样例#1: 复制

4
4 5 9 4

输出样例#1: 复制

43
54

 


思路:

一开始还以为和洛谷的合并果子一样是一道贪心题,不假思索就用了优先队列来每次贪心最大的两堆石头,后来看见题目上有一个限制: 每次只能合并相邻的两堆石头,果断放弃贪心。。。。。

看了题解发现是区间DP,要解决一些问题:

  • 需要将数组形成一个环,我们使用      a[i+n]=a[i];    也就是把数组长度增加一倍然后模拟环

分析一下题目:

  •       DP的禅语:求什么设什么     ; 所以设dp[ l ][ r ] 是第 l 到 r 堆范围的石子数量的最值
  •   根据题意可知每次都是相邻的两堆石子合并成一堆,这里可以得知枚举区间最小值是2
  •   这两堆石子又是由另外的石子合并的,那么我们可以认为 l 到 r 堆石子是由 dp[ l ][ k ] 和 dp[ k+1 ][ r ]合成的
  •        我们根据 k 的值将对应范围分成了两部分,然后根据公式会分成更多的部分

       此时我们还要得到该范围下的最值,所以需要对dp[ l ][ r ]的值进行更新, 完整的动态转移方程如下

dp[l][r]=max(dp[l][r] , dp[l][k]+dp[k+1][r]+w[r]-w[l-1]);	
  •         w[ r ] - w[ l-1 ] 就是我们更新数据的来源,表示从第 l ~ r 堆石子之和,先看看w是怎么来的:
	for(int i=1;i<=2*n;i++)
		w[i]=w[i]+w[i-1]; //前缀和!!!!
  •       这就是每个堆所在的位置的石头数量与它之前的所有石头数量的总和
  •       而  w[ r ] - w[ l-1 ]  减去w[ l-1 ] 是不能缺少的,因为 w[ l-1 ] 的数量已经被重复加了一次

还有几个注意的点:

  1. 第一层for用来枚举区间长度,第二层用来确定dp的 l 和 r 的范围 ,注意 r = l + x -1 < 2* n ,减一是防止重复加了l位置的数,还要注意dp的初始化
  2. k的范围是从 l 到 r ,左闭右开,因为k等于r就只有一堆了 

 


代码:

#include<iostream> 
#include<cstdio>
#include<algorithm>
#pragma GCC optimize(2)
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=200+10; 
int n;
int w[maxn],dp1[maxn][maxn],dp2[maxn][maxn];
//w[i]表示从第一堆到第i堆石子之和(即前缀和),则w[i]-w[j-1]表示从第j~i堆石子之和
//dp[i][j]存放的是从i~j的最值,1为最小值,2为最大值 
int main()
{
	std::ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
		w[i+n]=w[i];  //形成环 
	}
	for(int i=1;i<=2*n;i++)
		w[i]=w[i]+w[i-1]; //前缀和!!!! 
	
	//开始枚举区间长度
	//每次最少是2堆合并,所以最小值是2 
	for(int x=2;x<=n;x++)
		for(int l=1;l+x-1<2*n;l++)
		{//从左边的起点加上区间长度-1 小于整个环长度时 值就是右边的终点 
			int r=l+x-1;
			//每次都要重新初始化 
			dp1[l][r]=inf; dp2[l][r]=-inf;//分别是最小最大
			for(int k=l;k<r;k++)//k要在这个范围内,且不能等于r  等于r就只有一堆了 
			{
				dp1[l][r]=min(dp1[l][r],dp1[l][k]+dp1[k+1][r]+w[r]-w[l-1]);// w[r]-w[l-1] 一定要减去,不然就重复计算了 
				dp2[l][r]=max(dp2[l][r],dp2[l][k]+dp2[k+1][r]+w[r]-w[l-1]);		
			}	
		}	
	int ans1=inf,ans2=-inf;//分别是最小值和最大值 
	for(int i=1;i<=n;i++)
	{
		ans1=min(ans1,dp1[i][i+n-1]);//刚好是一个环的元素和 
		ans2=max(ans2,dp2[i][i+n-1]);
	}
	cout<<ans1<<endl<<ans2<<endl;	
	return 0;	
} 

 

 

 

 

 

 

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值