P2168 荷马史诗
P2168 荷马史诗
此题好像是Huffman树经典例题,不用哈夫曼树都对不起哈夫曼(bushi
0x00 Huffman树
-
定义:带权路径长度WPL最短的多叉树(最优多叉树)构造这种树的算法最早是由哈夫曼(Huffman)1952年提出,这种树在信息检索中很有用。
-
构树过程:每次取出根节点中权值最小的k个根节点进行合并,新的根节点权值为k个原根节点的权值之和
-
树的路径长度PL:从树根到树的每个节点的路径长度之和(节点数相同时,完全二叉树为这种路径长度最短的二叉树)。
-
树的带权路径长度WPL:树的所有叶子节点的带权路径长度(该节点到根节点路径长度与节点上权的乘积)之和。
-
哈弗曼编码: n个节点的哈夫曼树含有2n-1个节点,没有度为1的节点 编码从叶子节点到根节点,译码从根节点到叶子节点。
从哈夫曼树根节点开始,对左子树分配码“0”,右子树分配码“1”,一直到达叶子节点为止,然后将从树根沿每条路径到达叶子结点的代码排列起来,便得到了哈夫曼编码。
0x01 构建模型
什么时候用huffman树?
考虑它的性质:带权路径长度最小~
也就是\(点的权值\times点到根节点的距离\)最小
我们需要找四个东西:最值;权值;距离;叉数
在这个题里,最值就是编码的最短长度,权值就是单词出现次数,距离就是字符串\(s_i\)的长度,叉数就是几进制。
我们可以先根据单词出现次数构造Huffman树,然后利用它的性质求解
0x02 具体实现
因为在构树的时候,要找最小值,所以我们选用小根堆(优先队列)来存点的权值(莫名像合并果子(好吧就是
补节点:k叉Huffman树在最后合并时会出现根节点数小于k的情况,显然这不是最优解,所以我们要在构树之前用权值为0的点补上空位,使得
\[(总点数-1)\%(k-1)==0
\]
用pair叭别用结构体了
pair存当前点的权值和层数
0x03 代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<long long,long long> PLL;
typedef long long ll;
const int N=10001;
ll n,k,cnt,ans=0;
priority_queue<PLL,vector<PLL>,greater<PLL> > q;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
ll a;
cin>>a;
q.push({a,1});
}
while((q.size()-1)%(k-1)!=0) q.push({0,1});//补点
while(q.size()>=k){
ll h=-1,w=0;
for(int i=1;i<=k;i++){
PLL t=q.top();
q.pop();
h=max(h,t.second);
w+=t.first;
}
ans+=w;
q.push({w,h+1});
}
PLL res=q.top();
printf("%lld\n%lld\n",ans,res.second-1);
return 0;
}
好!