算法竞赛进阶指南——0x17【二叉堆】


在这里插入图片描述

模拟二叉堆

二叉堆是一种支持删除、插入、查询最值的数据结构,是满足堆性质的一颗完全二叉树,C++的priority_queue支持这些操作,但是不支持remove操作【以大根堆为例,注意这儿的n是堆中的元素个数,此n非彼n】

int n, head[N];       //层次序列存储方式
inline void up(int p) //p是下标
{                     //向上调整【大根堆为例】
    while (p > 1)
    {
        if (head[p] > head[p / 2])//小根堆则是head[p] < head[p / 2]
        {
            swap(head[p], head[p / 2]);
            p /= 2;
        }
        else
            break;
    }
}
inline void d_insert(int val)
{ //插入数值val
    head[++n] = val;
    up(n); //调节的参数是下标
}
inline int get_top() { return head[1]; } //取堆顶元素
inline void down(int p)
{ //向下调整【大根堆为例】
    int s = p * 2;
    while (s <= n)
    {
        if (s < n && head[s] < head[s + 1]) //左右子节点取较大者,小根堆则是s < n && head[s] > head[s + 1]
            s++;
        if (head[s] > head[p])//小根堆则是head[s] < head[p]
        {
            swap(head[s], head[p]);
            p = s, s = p * 2;
        }
        else
            break;
    }
}
inline void d_extract() //删除堆顶
{
    head[1] = head[n--];
    down(1);
}
inline void d_remove(int p) //删除下标p位置的结点
{
    head[p] = head[n--];
    up(p), down(p);
}

哈夫曼树【Huffman树】

有n个叶子结点的k叉树,i个叶子结点有权值wi,求最小化wi*Li,Li是第i个叶子结点到根节点的距离,该问题被称为k叉哈夫曼树,每个结点都有k-1个叶子节点【注意当k>2时,应该要补充一些额外的0权值的叶子结点,使得满足(n-1)%(k-1)==0,保证了贪心策略的正确性】
牛客一个小栗子荷马史诗

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
const int N = 1e5 + 10;
const int M = 3e6 + 10;
const int mod = 2147483647;
const int inf = 0x3f3f3f3f;
const int eps = 1e-6;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0)
ll n, k, ans, sum, dep;
pll a[N]; //first表示出现次数,second表示该结点在树中的深度
priority_queue<pll, vector<pll>, greater<pll>> p;
int main()
{
    IOS;
    cin >> n >> k;
    for (ll i = 1; i <= n; i++)
    {
        cin >> a[i].first;
        p.push({a[i].first, 0});
    }
    while ((n - 1) % (k - 1)) //额外加一些权值为0的叶子结点,保证贪心策略的正确性
    {
        p.push({0, 0});
        n++; //加上点之后总结点数要++
    }
    while (p.size() > 1)
    {
        sum = 0, dep = 0;
        for (int i = 0; i < k; i++) //k叉哈夫曼树,每个结点有k-1个是叶子节点
        {                           //k要多次循环利用,不能写成while(k--)
            pll x = p.top();
            p.pop();
            sum += x.first;
            dep = max(dep, x.second);
        }
        ans += sum;             //每次有结点会重复累加,所以这样的ans其实就是每个叶子节点的[权值*深度]的和
        p.push({sum, dep + 1}); //每次推入堆中的是合并后的值,并非总的值
    }
    cout << ans << "\n"
         << p.top().second << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WTcrazy _

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值