AcWing 149. 荷马史诗(“打补丁” 填充 k 叉哈夫曼树 贪心求最深节点深度最小值)

AcWing 149. 荷马史诗(“打补丁” 填充 k 叉哈夫曼树 贪心求最深节点深度最小值)_第1张图片
AcWing 149. 荷马史诗(“打补丁” 填充 k 叉哈夫曼树 贪心求最深节点深度最小值)_第2张图片
AcWing 149. 荷马史诗(“打补丁” 填充 k 叉哈夫曼树 贪心求最深节点深度最小值)_第3张图片

题意:

类似于 合并果子,但是 合并果子每次合并 2 堆(2 叉哈夫曼树),而本题是 每次最多合并 k 堆。(k 叉哈夫曼树)

共有两问:

  • 合并的最小权值和
  • 最深节点的深度最小值

思路:

首先我们分析 哈夫曼树 的一个经典性质:

  • 权值最小的节点一定尽可能在最下面一层

但是如果按上面的性质,面对下面的情况中会存在一些问题:

假设一棵哈夫曼树 共有 6 个节点每次合并 3 个节点,造成的影响是 总节点数每次少 2

最后一次合并的时候,我们只会 合并 2,稍作思考,下面这棵树显然不是最优的
AcWing 149. 荷马史诗(“打补丁” 填充 k 叉哈夫曼树 贪心求最深节点深度最小值)_第4张图片
原因:这棵树 最高处只有 2,我们其实可以 将其下方任意一个节点取下来并作为最高处节点的子节点。这样 代价一定会变的更小。

(这就相当于:其它的数均不变,将一个数从很深的一层转移到了最上方,转移节点权值 乘以的系数严格变小 的,而且其中 所有元素均为正

用一个式子表达出来:如果 n - m * (k - 1) = 1(即在 包含 n 个节点的哈夫曼树中,每次 合并 k 个节点,最终 恰好剩余 1 个节点),将该式子 转化一下(n - 1) % (k - 1) = 0

如果此式成立,那么之前我们提及的这个 经典性质 就是成立的

不成立的话,“将下方任意一个节点取下来并作为最高处节点的子节点,代价一定会变的更小”,这时我们就需要 “打补丁” 了。

“打补丁”具体做法等效的两种

由于上式不成立,那么 一定存在某个节点不够 k 个儿子,我们应当 倾向于选择最深处的节点使其少于 k 个儿子,因此我们 先计算出 (n - 1) % (k - 1) 的值 x第一次合并 x 个最小值 即可,除了第一次操作剩下部分 则每次 选择 k 个最小值 即可。

2 种做法 是一个与其 等价 的做法:直接往树中 填补上 x 个 值为 0 的节点使得 (n - 1) % (k - 1) = 0。(代码中的做法就是这个)

至此,第一问分析完毕。(最小权值和)

接下来分析 第二问:最深节点的深度最小值。

由于整个问题具有 最优子结构,因此我们 只需要考虑第一次合并的情况 即可,第一次合并 k 个节点 时,如果 k 个节点权值是严格最小 的(也就是说 k + 1 小的数k 个数 不相等),那么毫无疑问,k 个节点一定是在最下面且没有任何调整的余地

不过如果 有 k + 1 个数是 同时最小 的,如果 只看第 1 的话,合并这 k + 1 个节点中的任意 k 个节点得到的结果 都是 最小的。但是对于 2,我们一定得选择当前 深度比较小 的(即 子孙后代较少的),这样才能保证 当前合并出来的点深度是比较小 的(贪心)

综合一下,对于 1,每次我们选择 合并权值最小的 k 个数,加上 2,还是选择 权值最小的 k 个数,只不过 如果有多个权值一样的节点,则优先选择高度较小的(子孙较少的),即 双关键字排序。每一次合并操作完成之后,显然 形成的新节点高度所有子节点中高度的最大值加 1

代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
#define int long long
struct node
{
    int w;
    int depth;
    bool operator< (const node& x)const {
        if (w == x.w) return depth > x.depth;
        return w > x.w;
    }
};
int n, k;

signed main()
{
    int T = 1; //cin >> T;

    while (T--)
    {
        cin >> n >> k;
        priority_queue<node> heap;
        for (int i = 1; i <= n; ++i)
        {
            int w; scanf("%lld", &w);
            heap.push({ w, 0 });
        }

        while ((n - 1) % (k - 1))
            heap.push({ 0, 0 }), ++n;

        int cost = 0;

        while (heap.size() > 1)
        {
            int t = k;
            int tmp = 0;
            int dep = 0;
            while (t--)
            {
                auto h = heap.top(); heap.pop();
                tmp += h.w;
                dep = max(dep, h.depth + 1);
            }
            heap.push({ tmp, dep });
            cost += tmp;
        }

        cout << cost << '\n';
        cout << heap.top().depth << '\n';
    }

    return 0;
}

你可能感兴趣的:(刷刷蓝书,数据结构,贪心,算法,数据结构,c++)