习题:荷马史诗(哈夫曼树&贪心)

题目

传送门

思路

如果我们最后编码的答案建成一个trie树,

一条路径上除了末尾可以是一个编码的结尾

这条路径上的任何一个点都不可能是一个编码的结尾

因为需要满足一个编码不是另一个编码的前缀

如果我们视每一个点的点权为这个点出现的次数

每一个单词的编码为根节点到叶子节点上路径上的字符

也就是它的长度就是它的深度,也就是问题1可以表示为

\(ans=\sum_{i=1}^{n}dep_i*w_i\)

需要最小,

\(w_i\)是给定的,我们能做的只是调整每个编码的长度,

也就是末端节点再树上的深度

感性理解一下

就是\(w_i\)越大的点它的\(dep_i\)越小

\(w_i\)越小的点它的\(dep_i\)越大

我们还需要使最大的长度最短

很明显的一点,每一个节点的分支一定是尽可能多的

所以我们可以将\(w\)从小到大排序

用优先队列来维护

取出前k个点,将他们的权值累加,建一个新点,再将这个点重新塞回优先队列中

如此反复,直至优先队列中只剩下1个点

很明显权值越大的距离根节点的距离一定越近

如果是我们建的是虚点呢?也无妨

因为我们还需要保证一个编码不是另一个编码的前缀

此方法也可称为huffman编码

代码

#include<iostream>
#include<queue>
using namespace std;
struct node
{
	int dep;
	long long w;
	friend bool operator < (const node &a,const node &b)
	{
		if(a.w==b.w)
			return a.dep>b.dep;
		return a.w>b.w;
	}
}a[100005];
int n,k;
long long ans;
node cnt[15];
priority_queue<node> q;
int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		long long w;
		cin>>w;
		q.push((node){0,w});
	}
	while((n-1)%(k-1))
	{
		n++; 
		q.push((node){0,1ll*0});
	}
	while(q.size()!=1)
	{
		for(int i=1;i<=k;i++)
		{
			cnt[i]=q.top();
			q.pop();
		}
		node t;
		t.dep=0;
		t.w=0;
		for(int i=1;i<=k;i++)
		{
			t.w+=cnt[i].w;
			t.dep=max(t.dep,cnt[i].dep);
		}
		t.dep++;
		q.push(t);
		ans+=t.w;
	}
	cout<<ans<<' '<<q.top().dep;
	return 0;
}
posted @ 2019-12-21 14:34  loney_s  阅读(266)  评论(0)    收藏  举报