1019: 堆石子 2016年中南大学研究生复试机试题

题目描述

在一片沙滩上摆放着 n堆石子。 现要将石子有次序地合并成一堆。 规定每次选2 堆相邻石子合并成新的一堆,合并的费用为新的一堆石子数。试设计一个算法,计算出将 n堆石子合并成一堆的最小总费用。

输入

多组数据
输入数据第1行有1个正整数 n(1≤n≤300),表示有 n堆石子,每次选2堆石子合并。第2行有 n个整数, 分别表示每堆石子的个数(每堆石子的取值范围为[1,1000]) 。

输出

数据输出为一行, 表示对应输入的最小总费用。

样例输入

7
45 13 12 16 9 5 22

样例输出

313

解题思路

这题比较我觉得比较难想,可以用暴力, 但是我觉得暴力肯定会被卡掉,百度了一个算法叫GarsiaWachs算法。时间复杂度为O(n^2)。

它的步骤如下:

设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[0]和stone[n+1]是正无穷的。

举个例子:

186 64 35 32 103

因为35<103,所以最小的k是4,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面,得到:186 67 64 103,

现在由5个数变为4个数了,继续:因为67<103 ,所以k=3 ,删除67和64, 得到它们的和131 ,然后插入186后面,得到:186 131 103,

然后找到 k=2(别忘了,设stone[0]和stone[n+1]等于正无穷大)得到:234 186,最后得到420。最后的答案呢?就是各次合并的重量之和,即420+234+131+67=852。

 

基本思想是通过树的最优性得到一个节点间深度的约束,之后证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的深度不会改变。具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化,使得最终复杂度为O(nlogn)。

AC Code

#include<iostream>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 300 + 4;
int size=0, stone[MAXN];
void Erase(int first, int last){// delete elements in [first last)
	int t=last-first;
	for(int i=last; i<=size; ++i)
		stone[i-t]=stone[i];
	size-=t;
}
void Insert(int pos, int value){
	for(int i=size; i>=pos; --i)
		stone[i+1]=stone[i];
	stone[pos]=value;
	size+=1;
}
int main(){
    freopen("C:\\Users\\Ambition\\Desktop\\in.txt","r",stdin);
	int n;
	while(~scanf("%d", &n)){
		size=n+1, stone[0]=INF, stone[n+1]=INF-1;
		for(int i=1; i<=n; ++i) scanf("%d", &stone[i]);
		int heap=n, tmp, i, j, k, min_cost=0;
		while(heap>1){
			i=1;
			while(stone[i-1]>stone[i+1]) ++i;
			tmp=stone[i-1]+stone[i], min_cost+=tmp;
			k=i-1;
			Erase(k, k+2);
			j=k-1;
			while(stone[j]<tmp) --j;
			Insert(j+1, tmp);
			--heap;
		}
		printf("%d\n", min_cost);
	} 
	return 0;
}

参考博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值