农夫要修理牧场的一段栅栏,他测量了栅栏,发现需要N块木头,每块木头长度为整数L*i个长度单位,于是他购买了一条很长的、能锯成N块的木头,即该木头的长度是L*i的总和。

但是农夫自己没有锯子,请人锯木的酬金跟这段木头的长度成正比。为简单起见,不妨就设酬金等于所锯木头的长度。例如,要将长度为20的木头锯成长度为8、7和5的三段,第一次锯木头花费20,将木头锯成12和8;第二次锯木头花费12,将长度为12的木头锯成7和5,总花费为32。如果第一次将木头锯成15和5,则第二次锯木头花费15,总花费为35(大于32)。

请编写程序帮助农夫计算将木头锯成N块的最少花费。

输入格式:

输入首先给出正整数N(≤104),表示要将木头锯成N块。第二行给出N个正整数(≤50),表示每段木块的长度。

输出格式:

输出一个整数,即将木头锯成N块的最少花费。

输入样例:

1
2
8
4 5 1 2 1 3 1 1

输出样例:

1
49

思路

回忆下离散中或是人工智能数学基础中所学的最优二叉树,本题即为将给定的n个结点构建最优二叉树,输出非叶子结点值的和

根据最优二叉树(也叫赫夫曼树)的构建方法,我们每轮操作需要找到剩余结点中最小的结点和第二小的结点用于相加

如果每次暴力遍历寻找,复杂度为O(n2)

可以借助set的自动排序特性,因为结点值重复,所以需要multiset,复杂度可降为O(nlogn)

还可以借助priority_queue的小根堆,复杂度同样为O(nlogn)

知识点:最优二叉树(赫夫曼树)的构建方法、multiset、priority_queue(平时用的很少)

代码

如下为使用multiset,复杂度O(nlogn)。multiset底层是红黑树(改进的搜索树)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <set>

using namespace std;

int main() {
multiset<int> s;//结点值有重复,使用multiset
int n;
cin >> n;
while(n--) {
int id;
cin >> id;
s.insert(id);
}
int ret = 0;
while(s.size() > 1) {
int a = *(s.begin()); //第1小的数
s.erase(s.begin());
int b = *(s.begin()); //第2小的数
s.erase(s.begin());
s.insert(a + b);//生成一个非叶子结点
ret += a + b;//记录值
}
cout << ret << endl;
return 0;
}

如下为使用priority_queue,复杂度O(nlogn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <queue>

using namespace std;

int main() {
int n;
cin >> n;
priority_queue<int, vector<int>, greater<int>> heap; //小根堆
while(n--) {
int id;
cin >> id;
heap.push(id);
}
int ret = 0;
while(heap.size() > 1) {
int a = heap.top();//堆顶,第1小的数
heap.pop();
int b = heap.top();//堆顶,第2小的数
heap.pop();
heap.push(a + b);//生成一个非叶子结点
ret += a + b;//记录值
}
cout << ret << endl;
return 0;
}