其实我觉得这个东西还是比较玄学,主要是实现起来感觉不是难,重要的是学会如何建模.
定义
构造一棵树使一棵树中点的权值与深度的乘积和最小
\(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树的适用范围,然后更多的话就需要在题目中自行探索了.
来源:https://www.cnblogs.com/cjgjh/p/9511926.html