其实我觉得这个东西还是比较玄学,主要是实现起来感觉不是难,重要的是学会如何建模.
定义
构造一棵树使一棵树中点的权值与深度的乘积和最小
\(Min\sum_{i}val[i]*dep[i](i∈V)\)
那么我们如何构造一棵这样的树呢?显然对于每一个点,只有权值是给出的,我们yy一下就发现,只有当权值大的树的深度尽量小那么才能够保证权值和比较小.所以我们可以用优先队列|堆完成这个任务
但是一般的题目不会让你写Huffman树的裸题,那么显然,构造出来的树的分叉也不一定只有两个,所以我们要掌握很多东西.
结论1
若一颗Huffman树是满k叉树,那么显然(n-1)%(k-1)必然为0.
这个的证明其实还是比较简单,因为你将根节点除去后的图必然有k的倍数个分叉,这才满足是一个满k叉树
结论2
权值越大的放到深度小的才能够构成Huffman树.
这个就是依据Huffman树的定义来的.
模板
讲了这么多,那么Huffman树究竟有没有一个固定的模板呢?答案是有的
struct node{
int val,dep;
bool operator<(node b){
return val>b.val || val==b.val && dep>b.dep;//显然深度大的如果不先合并它就会变得越来越大!
}
}
priority_queue<node>q;
void Huffman(int n,int k){//一共有n个点,k叉Huffman树
ll sum=0;
if((n-1)%(k-1))sum=(k-1)-(n-1)%(k-1);//需要补充的节点
for(int i=1;i<=sum;i++){
node need;
need.dep=1;need.val=0;//补充val为0的节点显然对答案没有影响.
q.push(need);
}
sum+=n;
while(sum>1){//还没到根节点
node a;int mx,now=0;
for(int i=1;i<=k;i++){
node now=q.top();mx=max(mx,q.dep);
now+=q.val();q.pop();
}
a.val=now;ans+=now;//合并后的权值
a.dep=mx+1;q.push(a);//合并后的深度,搞好后放进堆
sum-=(k-1)//搞好了k-1个节点
}
//最后的合并的结果存储在ans里面
}
例题1 NOIP2004 合并果子
这道题目显然是Huffman树的裸题,我们每次搞两个数合并,然后合并出来的树权值变大,那么最优解就满足Huffman树的定义,那么就可以这么搞.
#include<stdio.h>
#include<stdlib.h>
#include<queue>
#define re register
#define ll long long
using namespace std;
int n;
priority_queue<int >q;
int main(){
scanf("%d",&n);
for(re int i=0;i<n;i++){
int a;scanf("%d",&a);
q.push(-a);
}
int ans=0;
while(q.size()>1){
int del1=q.top();q.pop();
int del2=q.top();q.pop();
ans-=del1+del2;
q.push(del1+del2);//这里维护的是2叉
}
printf("%d\n",ans);
return 0;
}
例题2 NOI2015 荷马史诗
显然为了使总长度最小,且每一个点有属于自己的val,很容易想到Huffman树,那么我们就可以理所当然的构造一棵,然后在这颗Huffman树里寻找出需要的答案.
#include<stdio.h>
#include<algorithm>
#include<queue>
#define re register
#define ll long long
using namespace std;
struct node{
ll dep,val;
bool operator<(node b)const{
return val>b.val || val==b.val && dep>b.dep;
}
};
priority_queue<node>q;
ll n,k;
int main()
{
scanf("%lld%lld",&n,&k);
for(re ll i=1;i<=n;i++){
node a;a.dep=1;
scanf("%lld",&a.val);
q.push(a);
}
ll bu=0;
if((n-1)%(k-1)==0);
else bu+=k-1-(n-1)%(k-1);
for(re ll i=1;i<=bu;i++){
node a;a.dep=1;a.val=0;
q.push(a);
}
bu+=n;ll ans=0;
while(bu>1){
node a;ll redep=0,nowa=0;
for(re ll i=1;i<=k;i++){
node now=q.top();nowa+=now.val;
redep=max(redep,now.dep);q.pop();
}
bu-=k-1;
ans+=nowa;a.dep=redep+1;
a.val=nowa;
q.push(a);
}
printf("%lld\n%lld\n",ans,q.top().dep-1);
return 0;
}
习题(待补充)
总结
那么这一类题目在题面中一般会告诉我们k和要我们求出一个最小值,那么这就是Huffman树的适用范围,然后更多的话就需要在题目中自行探索了.