修理牧场
农夫要修理牧场的一段栅栏,他测量了栅栏,发现需要N块木头,每块木头长度为整数Li个长度单位,于是他购买了一条很长的、能锯成N块的木头,即该木头的长度是Li的总和。
但是农夫自己没有锯子,请人锯木的酬金跟这段木头的长度成正比。为简单起见,不妨就设酬金等于所锯木头的长度。例如,要将长度为20的木头锯成长度为8、7和5的三段,第一次锯木头花费20,将木头锯成12和8;第二次锯木头花费12,将长度为12的木头锯成7和5,总花费为32。如果第一次将木头锯成15和5,则第二次锯木头花费15,总花费为35(大于32)。
请编写程序帮助农夫计算将木头锯成N块的最少花费。
输入格式:
输入首先给出正整数N(≤104),表示要将木头锯成N块。第二行给出N个正整数(≤50),表示每段木块的长度。
输出格式:
输出一个整数,即将木头锯成N块的最少花费。
输入样例:
8
4 5 1 2 1 3 1 1
输出样例:
49
思路:将锯木头的过程逆过来看的话,就是从N块木头中每次挑出两个最短的合成为一个,再从这N-1个木头中重复合成,直至剩一个木头。那么最少花费就是各个合成的木头长度的和。
这个过程显然与求解赫夫曼树大同小异,这里采用最小堆实现的优先队列求解。
最小堆及优先队列
最小堆实现优先队列有不同方法,这里参考的是《算法导论(第三版)》第六章堆排序。
基础知识: 最小堆一般用数组表示,可以证明,其中第i(1 ≤ i ≤ n) 个元素的父节点PARENT(i) = i / 2,左孩子LEFT(i) = i * 2, 右孩子RIGHT(i) = i * 2 + 1。
首先假定以节点i的左孩子为顶点的子树,右孩子为顶点的子树都是最小堆,若结点i不满足最小堆性质,则让该节点的值逐级下降,直至满足最小堆的性质:
void heapfy(int a[], int i, int length){
int mins = i;
int l = i * 2;
int r = i * 2 + 1;
if(l <= length && a[l] < a[i]) mins = l;
if(r <= length && a[r] < a[mins]) mins = r;
if(mins != i){
int temp = a[i];
a[i] = a[mins];
a[mins] = temp;
heapfy(a, mins, length);
}
}
由该算法可以维护一个基本有序的最小堆,但是,我们知道,i = n / 2 + 1, n / 2 + 2,....n都是叶节点,如果从n / 2 降至1的过程中都调用该函数,就可以将任给的数组建为最小堆:
void buildHeap(int a[], int length){
for(int i = length / 2; i > 0; i--){
heapfy(a, i, length);
}
}
建立最小堆之后,可以实现队列的出队和入队:
int heapPop(int a[], int& length)//出队
{
int temp = a[1];
a[1] = a[length];//将队尾结点值复制到队首
heapfy(a, 1, --length);//维护长度减一的堆
return temp;
}
通过void decreaseInsert(int a[], int i, int key)可以实现入队:
void decreaseInsert(int a[], int i, int key)//将结点i的值更新为更小值key
{
if(a[i] < key) return;
a[i] = key;
while(i > 1 && a[i] < a[i / 2]){ //更新后的值可能会违反最小堆的性质,若此则逐级递升,直至符合最小堆性质
int temp = a[i];
a[i] = a[i / 2];
a[i / 2] = temp;
i /= 2;
}
}
由此,只需在队尾新增极大值结点, 再调用上函数即可实现入队:
void insertHeap(int a[], int key, int& length){
length++;
a[length] = 9999999;
decreaseInsert(a, length, key);
}
至此已经可以解决修理牧场问题了。
另外C++ STL提供了优先队列的实现,稍加修改即可方便地实现最小优先队列,在此附上两个版本的源代码。
源代码:
#include <cstdio>
void heapfy(int a[], int i, int length){
int mins = i;
int l = i * 2;
int r = i * 2 + 1;
if(l <= length && a[l] < a[i]) mins = l;
if(r <= length && a[r] < a[mins]) mins = r;
if(mins != i){
int temp = a[i];
a[i] = a[mins];
a[mins] = temp;
heapfy(a, mins, length);
}
}
void buildHeap(int a[], int length){
for(int i = length / 2; i > 0; i--){
heapfy(a, i, length);
}
}
int heapPop(int a[], int& length){
int temp = a[1];
a[1] = a[length];
heapfy(a, 1, --length);
return temp;
}
void decreaseInsert(int a[], int i, int key){
if(a[i] < key) return;
a[i] = key;
while(i > 1 && a[i] < a[i / 2]){
int temp = a[i];
a[i] = a[i / 2];
a[i / 2] = temp;
i /= 2;
}
}
void insertHeap(int a[], int key, int& length){
length++;
a[length] = 9999999;
decreaseInsert(a, length, key);
}
int a[10010] = {0};
int main(){
int m, n;
scanf(" %d", &n);
int length = n;
for(int i = 1; i != n+1; ++i){
scanf(" %d", &a[i]);
}
buildHeap(a, length);
int sum = 0;
while(length > 1){
int x = heapPop(a, length);
int y = heapPop(a, length);
insertHeap(a, x+y, length);
sum += x+y;
}
printf("%d", sum);
}
使用STL:
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
int main(){
priority_queue<int,vector<int>,greater<int> >l;
int n,a;
scanf("%d", &n);
for (int i=0;i<n;i++){
scanf("%d", &a);
l.push(a);
}
int sum=0;
while(l.size()!=1){
int x=l.top();
l.pop();
int y=l.top();
l.pop();
sum+=x+y;
l.push(x+y);
}
printf("%d\n",sum);
return 0;
}