Bread(哈夫曼树)
题意
有一条长度为\(L\)的面包,将会被切开分配给\(N\)个小朋友。
第\(i\)个小朋友希望获得长度为\(A_i\)的面包。
现在需要重复下面的操作,获得长度为\(A_1, A_2, \dots, A_N\)的面包:选择长度为\(k\)的面包和一个正整数\(x\),将面包切成\(x\)和\(k - x\)两段,花费代价为\(k\)。
每一个小朋友\(i\)获得的面包必须正好是\(A_i\),但是允许剩下一些面包没有被分配。求最小的代价。
数据范围
\(2 \leq N \leq 2 \times 10^5\)
\(1 \leq A_i \leq 10^9\)
\(A_1 + A_2 + \dots + A_N \leq L \leq 10^{15}\)
思路
当\(L = A_1 + A_2 + \dots + A_N\)时,我们考虑操作的逆过程。我们需要将\(N\)个价值合并起来,最终合并成长度\(L\)。其中每次操作花费为两个价值之和。
显然逆过程的最小花费与题目中过程的最小花费相等。这样就转化成了一个经典的贪心问题:合并果子,即使用优先队列每次合并两个最小价值。
现在考虑\(L > A_1 + A_2 + \dots + A_N\)的情况,其逆过程即为将\(A_1, A_2, \dots, A_N, B_1, B_2, \dots, B_M\)合并成\(L\)。其中,\(B_1 + B_2 + \dots + B_M = L - (A_1 + A_2 + \dots + A_N)\)。
显然,当\(M = 1\)并且\(B_1 = L - (A_1 + A_2 + \dots + A_N)\)时花费最小。然后对上述\(n+1\)个价值使用优先队列合并即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 200010;
int n;
ll L;
ll a[N];
int main()
{
scanf("%d%lld", &n, &L);
ll sum = 0;
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
sum += a[i];
}
if(L > sum) {
n ++;
a[n] = L - sum;
}
priority_queue<ll, vector<ll>, greater<ll> > heap;
for(int i = 1; i <= n; i ++) heap.push(a[i]);
ll ans = 0;
for(int i = 1; i < n; i ++) {
ll x = heap.top();
heap.pop();
ll y = heap.top();
heap.pop();
ans += x + y;
heap.push(x + y);
}
printf("%lld\n", ans);
return 0;
}