合并石子(动态规划)

Description

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

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

Input
数据的第 1行是正整数 N,表示有 N 堆石子, N <= 500。

第 2 行有 N 个整数,第 i个整数 ai 表示第 i 堆石子的个数, ai <= 50。

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

Sample Input
4
4 5 9 4
Sample Output
43
54

首先理解题意,笔者看到这道题时首先想到的是贪心,仔细分析后发现题上没说石子根据一定顺序来摆放,易证局部最优解不一定是问题最优解,没有贪心性质,不能用贪心

然后需要强调的是,该题是一个圆形结构

这里为了方便理解,我们先将其简化为线性结构

先看一个简单的子问题

现在一共有三堆石头,每堆石子的数量分别是3,4,11。求合并成一堆石头的最小得分。
对于这个问题,只有两种选择:
前两堆石头合并再和第三堆石头合并。
3+4=7 ——> 7 石堆变为(7, 11)
7+11=18——>18 石堆变为(18)
cost=7+18=25
后两堆石头合并再和第一堆石头合并。
4+11=15——>15 石堆变为(3,15)
3+15=18——>18 石堆变为(18)
cost=15+18=33

易看出先合并前两堆的石子的花费比较小,不同的合并方式会造成不同的得分。同时可以发现这两种方法最后一次的得分就是石头的总数

现在我们用一个数组arr[]按顺序保存每个位置的石头数量。
arr[]={3,4,11}

前缀和数组sum[i]表示前i堆石头的总和
sum[0]=3;
sum[1]=7;
sum[2]=18;

最后用一个二维数组dp[i][j]表示从第i堆到第j堆石头合并的最小分数。
1)每堆石头跟自己合并的分数都是0。
dp[0][0]=dp[1][1]=dp[2][2]=0
2)相邻两堆石头的最小合并分数只有一种可能。
dp[0][1]=3+4=7, dp[1][2]=4+11=15
3)剩下的就是不相邻石头的合并最小花费。
设一个k∈[i,j];
dp[i][j]=dp[i][k]+dp[k][j]+sum[i][j];
那么从i到j的所有花费都可以表示出来了,取一个使得dp[i][j]最小的值。
求最大花费时同理,只需将相关变量进行改变即可

最后回到原始问题,我们是一个圆形结构,此时

输入4 5 9 4,还要考虑4 4 5 9,9 4 4 5和5 9 4 4

因此我们每次都改变输入数组,最后最小取得最小,最大取得最大即可

详情可以看代码

#include<cstdio>
#include<algorithm>

using namespace std;

int dp1[505][505];//定义dp1[i][j]表示从第i堆到第j堆石子合并的最小得分 
int dp2[505][505];//定义dp1[i][j]表示从第i堆到第j堆石子合并的最大得分 
int n,maxn=0x3f3f3f3f,ans1=maxn,ans2=0;
int arr[505],sum[505];

void pc(){
	int temp=arr[0];
    for(int i=0;i<n;i++) arr[i]=arr[i+1];
    arr[n-1]=temp;
} //满足环形排列

int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&arr[i]);
	}	
	for(int count=1;count<=n;count++){//4 5 9 4    5 9 4 4   9 4 4 5   4 4 5 9
//		for(int i=0;i<n;i++){
//			printf("%d ",arr[i]);
//		}
//		printf("\n");
		sum[0]=arr[0];
		for(int i=1;i<n;i++){
			sum[i]=sum[i-1]+arr[i];//前缀和数组 
		} 
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				dp1[i][j]=maxn;//求最小得分因此初始化为最大值 
				dp2[i][j]=0;//求最大得分因此初始化为最小值 
				if(i==j){//最小子问题 自己不能和自己合并因此i=j时初始化为0 
					dp1[i][j]=dp2[i][j]=0;
				}
			}
		}
		for(int k=2;k<=n;k++){//从大小为2的子问题开始求解 
			for(int i=0;i<n;i++){//起点 
				int j=i+k-1;//终点 
				if(k==2){//只有两堆石子时结果就是两堆石子相加
					dp1[i][j]=arr[i]+arr[j];
					dp2[i][j]=arr[i]+arr[j];
				}else{
					int temp=sum[j]-sum[i-1];//最后一次合并固定加上所有数之和 
					for(int t=i;t<j;t++){
						dp1[i][j]=min(dp1[i][j],dp1[i][t]+dp1[t+1][j]+temp);
						dp2[i][j]=max(dp2[i][j],dp2[i][t]+dp2[t+1][j]+temp);
					}
				}
			}
		}
//		printf("求最小\n");
//		for(int i=0;i<n;i++){
//			for(int j=0;j<n;j++){
//				printf("%d ",dp1[i][j]);
//			}
//			printf("\n");
//		}		
//		printf("求最大\n");
//		for(int i=0;i<n;i++){
//			for(int j=0;j<n;j++){
//				printf("%d ",dp2[i][j]);
//			}
//			printf("\n");
//		}		
		//最后在所有的情况中取最小值即可
		ans1=min(ans1,dp1[0][n-1]);
		ans2=max(ans2,dp2[0][n-1]);
		pc(); 		
	}
	printf("%d\n%d\n",ans1,ans2);

	return 0;
} 

读者可以将注释中的内容解开调试方便理解

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ZZZWWWFFF_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值