P2168 荷马史诗

P2168 荷马史诗

此题好像是Huffman树经典例题,不用哈夫曼树都对不起哈夫曼(bushi

0x00 Huffman树

  1. 定义:带权路径长度WPL最短的多叉树(最优多叉树)构造这种树的算法最早是由哈夫曼(Huffman)1952年提出,这种树在信息检索中很有用。

  2. 构树过程:每次取出根节点中权值最小的k个根节点进行合并,新的根节点权值为k个原根节点的权值之和

  3. 树的路径长度PL:从树根到树的每个节点的路径长度之和(节点数相同时,完全二叉树为这种路径长度最短的二叉树)。

  4. 树的带权路径长度WPL:树的所有叶子节点的带权路径长度(该节点到根节点路径长度与节点上权的乘积)之和。

  5. 哈弗曼编码: 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;
}

好!

posted @ 2021-02-05 22:38  wsy_jim  阅读(92)  评论(0编辑  收藏  举报