Huffman树——operator_

前言

发现很多简单,但比较偏的算法我都不懂,所以现在开始按随机跳的题来浅浅了解一下这些东西。

正文

例题:P2168 [NOI2015] 荷马史诗

随机跳到这一题,感觉是贪心,但没有具体思路,看了题解发现是个叫 H u f f m a n Huffman Huffman 的东西,去了解了一下。

算法概述

也可以说是一个数据结构。

它可以用一句话来描述:带权路径长度最短的二(多)叉树。

一棵树的带权路径长度是 每个节点的权值*这个点到根的距离之和

二叉Huffman树的构造

假设现在有 n n n 个有一定权值的点,那么如何构造出一棵二叉 H u f f m a n Huffman Huffman 树?

方法是这样的:

先默认每个点都是一棵权值为它自己,深度为 1 1 1 的树。

每次选择权值最小,深度较小的两棵树,将其合并,合并的方法是将这 2 2 2 棵树的根放在同一个新建根节点下,显然,该树的权值为两棵树的权值和,深度为两棵树中深度较大的再 + 1 +1 +1

重复执行,直到只剩一棵树了,这就是最后的 H u f f m a n Huffman Huffman 树。

使用优先队列维护。

怎么理解这个过程?可以把每棵树看成一个整体,那么显然优先合并的树,其在最终的深度会尽量的比后合并的树大一点,那么自然是权值小一点的树放在深度较大的地方比较好。

多叉Huffman树的构造

有了上面的过程,我们可以类比得到多叉树的维护方式:每次选前 k k k 个权值较小的。

不过这是错误的,原因是如果我们这样做,可能会出现这样的情况:前面合并的很开心,最后一步却发现只剩下不到 k k k 棵树了,那怎么办?强制合并吗?那显然不是最优的,因为任何一个点补充上去都会使情况更优。

所以有了一个神奇的方法:加 0 0 0

首先,易证不管加多少个 0 0 0 ,只要足够,对答案是没有影响的,因为多余的 0 0 0 一定会被放在叶子结点上。

那么多少个 0 0 0 就足够了呢?我们补 0 0 0 的原因是防止最后只剩不到 k k k 棵树,所以补到 ( n − 1 )   m o d   ( k − 1 ) ! = 0 (n-1)\bmod(k-1)!=0 (n1)mod(k1)!=0 时即可。

引申:Huffman编码

初赛好像考过。

问题:给一个字符串,试给每个字符确定一个唯一的 01 01 01 串,使替换后的字符串长度最短。

其实就是将每个字符的出现次数定义为它的权值,求一个二叉 H u f f m a n Huffman Huffman 树即可,将每个点去左儿子的边设成 0 0 0 ,去右儿子的设成 1 1 1 ,最后每个字符的 01 01 01 串就是它到根的路径。

本题解答

就是求一个 k k k 进制的 H u f f m a n Huffman Huffman 编码。

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read() {
	int s=0,m=0;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-')m=1;ch=getchar();}
	while( isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return m?-s:s;
}
struct QWQ{
	int dep,w;
	bool operator <(const QWQ &x) const{
		if(w==x.w) 
			return dep>x.dep;
		return w>x.w; 
	}
};
priority_queue<QWQ> pq;
int n,k,ans;
signed main() {
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		pq.push({1,read()});
	while((pq.size()-1)%(k-1)) 
		pq.push({1,0});
	while(pq.size()>=k) {
		QWQ t={0,0};
		for(int i=1;i<=k;i++) {
			QWQ f=pq.top();pq.pop();
			t.dep=max(t.dep,f.dep),t.w+=f.w;
		}
		ans+=t.w,t.dep++;
		pq.push(t);
	}
	printf("%lld\n%lld",ans,pq.top().dep-1);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值