洛谷 P2168 [NOI2015]荷马史诗(Huffman树|编码)

题目链接:https://www.luogu.com.cn/problem/P2168

哈夫曼编码:

有n个字符,a,b,c,...,每个字符都有一个频率,$f_a,f_b,f_c...$,用k进制数表示字符a,b,c...

设计编码,使得总长度Len最短,其中$Len=f_a imes l_a+f_b imes l_b...$

不难发现,如果每个字符都有自己的表达方式,那么任意两个编码之间不能是对方的前缀,表现在树上就是不能是它的祖先(不在到根节点的路径上)。

性质:1.字符是叶节点。

   2.非叶节点有k个子节点。(这样一定是最优的)

   2‘.非叶节点中子节点数量不是k的至多有一个。(当(n-1)%(k-1)==0时没有)

    所以可以添加一些(k-1-(n-1)%(k-1))f为0的字符,使其满足性质2,且不会产生影响。

所以在k进制下,最优的操作就是每次取前k个最小的,合并。

例题:

1.合并果子

题目链接:https://www.luogu.com.cn/problem/P1090

其实可以看成是一个2进制下的哈夫曼编码问题,搬运花费即为频率,每次合并取前两个最小的。

2.荷马史诗

题目链接:https://www.luogu.com.cn/problem/P2168

 k进制下的哈夫曼编码问题。首先看能否满足性质二,不满足则用w为0的字符补全,然后每次取k个合并(想哈夫曼树),即得答案。

AC代码:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<queue>
 4 #include<algorithm>
 5 using namespace std;
 6 typedef long long ll;
 7 ll n,k,cnt,sum,ans;
 8 struct node{
 9     ll w,h;
10 };
11 bool operator<(node a,node b){
12     if(a.w!=b.w) return a.w>b.w;
13     return a.h>b.h;
14 }
15 priority_queue<node> q;
16 int main(){
17     scanf("%lld%lld",&n,&k);
18     for(int i=1;i<=n;i++){
19         node t;
20         scanf("%lld",&t.w);
21         t.h=1;
22         q.push(t);
23     }
24     ll top=0;
25     if((n-1)%(k-1)!=0){
26         int t=k-1-(n-1)%(k-1);
27         top+=t;
28         for(int i=1;i<=t;i++){
29             node t;
30             t.w=0;t.h=1;
31             q.push(t);
32         }
33     }
34     top+=n;
35     while(top!=1){
36         ll maxh=0;
37         sum=0;
38         for(int i=1;i<=k;i++){
39             node t;
40             t=q.top();
41             q.pop();
42             maxh=max(maxh,t.h);
43             sum+=t.w;
44         }
45         ans+=sum;
46         node t;
47         t.w=sum; t.h=maxh+1;
48         q.push(t);
49         top-=k-1;
50     }
51     printf("%lld
%lld",ans,q.top().h-1);
52     return 0;
53 }
AC代码
原文地址:https://www.cnblogs.com/New-ljx/p/13760110.html