题解:AcWing 148 合并果子
【题目来源】
AcWing:148 合并果子 - AcWing题库
【题目描述】
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 \(n-1\) 次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 \(1\) ,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 \(3\) 种果子,数目依次为 \(1\) ,\(2\) ,\(9\)。可以先将 \(1\) 、 \(2\) 堆合并,新堆数目为 \(3\) ,耗费体力为 \(3\)。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 \(12\),耗费体力为 \(12\)。所以多多总共耗费体力 \(=3+12=15\)。可以证明 \(15\) 为最小的体力耗费值。
【输入】
共两行。第一行是一个整数 \(n(1\le n\le 10000)\) ,表示果子的种类数。
第二行包含 \(n\) 个整数,用空格分隔,第 \(i\) 个整数 \(a_i(1\le a_i\le 20000)\) 是第 \(i\) 种果子的数目。
【输出】
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 \(2^{31}\)。
【输入样例】
3
1 2 9
【输出样例】
15
【核心思想】
-
问题分析:给定 \(n\) 堆果子,每堆有 \(a_i\) 个果子。每次可以将两堆果子合并,代价为两堆果子数量之和。经过 \(n-1\) 次合并后只剩一堆,求最小总代价。这是一个经典的哈夫曼贪心问题,关键在于每次选择数量最少的两堆进行合并。
-
算法选择:
- 哈夫曼贪心策略(Huffman Greedy):每次选择数量最少的两堆合并,确保大堆尽量少参与合并
- 小根堆(优先队列):使用
priority_queue维护当前所有堆的数量,快速获取最小值 - 贪心正确性:每次合并最小的两堆,可以使大堆的合并次数最少,从而最小化总代价
-
关键步骤:
- 读取输入:\(n\)(果子种类数)、\(a_1, a_2, \ldots, a_n\)(每堆果子数量)
- 初始化小根堆:将所有 \(a_i\) 加入小根堆
- 贪心合并(重复 \(n-1\) 次):
- 取出最小两堆:
x = heap.top(),y = heap.top() - 计算代价:
res += x + y - 合并入堆:
heap.push(x + y)
- 取出最小两堆:
- 输出结果:最小总代价 \(res\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),每次堆操作 \(O(\log n)\),共 \(n-1\) 次合并
- 空间复杂度:\(O(n)\),小根堆存储所有堆的数量
-
哈夫曼贪心的核心思想:
- 局部最优选择:每次选择最小的两堆合并,这是当前最优的选择
- 全局最优保证:通过数学归纳法可以证明,这种贪心策略能得到全局最优解
- 堆结构优化:使用小根堆可以在 \(O(\log n)\) 时间内获取最小值,高效支持贪心选择
- 类比哈夫曼编码:与构建哈夫曼树的策略相同,频率低的字符编码长,频率高的编码短
- 适用于最优合并、最优编码、带权路径长度最小化等问题
【算法标签】
贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n; // 输入的数字个数
cin >> n;
// 定义一个小根堆(优先队列),用于存储数字并快速获取最小值
priority_queue<int, vector<int>, greater<int>> heap;
// 输入 n 个数字,并将它们加入小根堆
while (n--)
{
int x;
cin >> x;
heap.push(x); // 将数字 x 加入小根堆
}
int res = 0; // 用于存储最终的结果(最小代价)
// 当堆中还有多于一个元素时,继续合并
while (heap.size() > 1)
{
// 取出堆中的两个最小元素
int x = heap.top(); heap.pop(); // 取出堆顶元素(最小值)并删除
int y = heap.top(); heap.pop(); // 再次取出堆顶元素(次小值)并删除
// 将这两个元素合并,并将合并的代价累加到结果中
res += x + y; // 合并代价为 x + y
// 将合并后的新元素(x + y)重新加入堆中
heap.push(x + y);
}
// 输出最终的最小代价
cout << res << endl;
return 0;
}
【运行结果】
3
1 2 9
15
浙公网安备 33010602011771号