当前位置: 首页 > article >正文

合并石子(动态规划)

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;
} 

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


http://www.kler.cn/a/15750.html

相关文章:

  • Spring 4.3 源码导读
  • Python作业05
  • vue3【实战】切换全屏【组件封装】FullScreen.vue
  • 一文详细深入总结服务器选型
  • reduce-scatter:适合分布式计算;Reduce、LayerNorm和Broadcast算子的执行顺序对计算结果的影响,以及它们对资源消耗的影响
  • vue项目PC端和移动端实现在线预览pptx文件
  • DPDK系列之十六虚拟化virtio源码分析之virtio-user
  • JS手撕代码系列【手写实现Promise】
  • 【Redis16】Redis进阶:内存优化
  • wifi芯片行业信息汇总
  • AcWing55. 连续子数组的最大和
  • 【柒志科技】面经 base上海
  • 了解hiberfil.sys文件:计算机休眠模式的背后
  • 【数据治理】数据治理的定义和价值
  • 标准错误重定向
  • 2023-04-30:用go语言重写ffmpeg的resampling_audio.c示例,它实现了音频重采样的功能。
  • 在全志V851S开发板上使用SSH配置步骤分析
  • 前端小白是如何利用chatgt用一周时间从做一款微信小程序的
  • 【MATLAB数据处理实用案例详解(15)】——利用BP神经网络实现个人信贷信用评估
  • 零基础想成为黑客,只需要四步
  • CF662C Binary Table
  • nvm安装使用详解,附gnvm介绍
  • 史上最全的接口测试,吐血整理从零到接口自动化实战...
  • 1992-2022年31省人均gdp/各省人均地区生产总值
  • @PostConstruct注解和@PreDestroy注解
  • 【AI生产力工具】Upscale.media:用AI技术提升照片质量,让你的作品更出色