题目描述
在一片沙滩上摆放着 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;
}