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;
}
posted @ 2022-06-01 15:37  pbc的成长之路  阅读(224)  评论(0编辑  收藏  举报