《算法竞赛进阶指南》0x17二叉堆 利用优先队列求k叉哈夫曼树的最优结构

题目链接:https://www.acwing.com/problem/content/description/151/

给定长度为n的序列,代表一个单词的出现次数,要求构造k叉哈夫曼树使得总权值最小,并且在权值最小的情况下问最小的高度是多少?

我们可以考虑不断取k个数组成一个新的结点放入优先队列,但是根节点可能不满k叉,所以可以考虑在其中不超过k-1个零来上调权值比较大的结点,权值为零的结点肯定在最深层的一个父节点上。

也只有这一个父节点是不满k叉的,这个可以通过贪心的思想进行验证,接下来考虑深度最小,我们在合并的时候,值不同的节点在同一轮取数中取出来之后无法更优,只能按照顺序拼接,但是

如果结点的值一样的时候我们需要优先选择高度比较小的来合并,因为我们的哈夫曼树是从下到上构建的,高度大的数可以放到下一轮合并中,可以证明这样的选择更优,故在优先级队列中我们

需要维护两个关键字,一个是当前树的权重和,一个是高度,使用pair<long long ,int>存储。

其次,每一轮取数中height的更新方式是,新构造的结点的height是子节点的height的最大值+1,注意取出来的结点一定是已经满足上面两种关键字最优性的。

代码:

#include<iostream>
#include<cstdio>
#include<queue>
typedef long long ll;
#define  PLI pair<ll,int>
using namespace std;
int main(){
    int n,k;
    cin>>n>>k;
    priority_queue<PLI,vector<PLI>,greater<PLI> > pq; 
    for(int i=0;i<n;i++){
        ll x;
        cin>>x;
        pq.push({x,0});
    }
    while((n-1)%(k-1))n++,pq.push({0,0});//保证k叉树每个结点都有k个叉,并且最深处的结点存在为0的点
    ll res=0;
    while(pq.size()>1){
        ll s=0;
        int height=0;
        for(int i=0;i<k;i++){//构造一个结点 
            PLI p=pq.top();
            s+=p.first;
            height=max(height,p.second);//计算结点的高度,等于子节点的最大高度加一 
            pq.pop();
        }
        res+=s;
        pq.push({s,height+1}); 
    } 
    cout<<res<<endl<<pq.top().second<<endl;
} 
原文地址:https://www.cnblogs.com/randy-lo/p/13158129.html