NOI2015 荷马史诗

题目链接:NOI2015 荷马史诗

在写题解之前先扯一些闲话

初识这题是在hzwer的博客上,他写的一篇类似于回忆录中提到了这题。其实OI中有很多类似于哈夫曼的知识——它们看起来看起来不是那么重要,但是如果一旦用到,对于那些不知道的人便是”灭顶之灾“,OI这玩意,很多时候,真的有一部分运气的成分,五六场比赛,不能出现一丝错误,虽然不乏奇迹,但更多的,却是无奈。

哈夫曼编码是基于哈夫曼树的一种编码方式,注意到题目有这样一句话

“对于任意的 1 ≤ i, j ≤ n , i ≠ j ,都有:si不是sj的前缀。”

然后我们在来看到一个哈夫曼编码的例子:

 



大家所熟知的哈夫曼树基本上都是二叉的,即用二进制表示

而题目中要求的是k进制,这基本上是一样的

在上面的例子中,所有有字母的格子都代表着未加密的字符,由根节点走向它们的路径上的数组成了这个字符的编码,由于每个字符没有子树,所以便不会出现有编码是其它编码的前缀

由于此时的编码是k进制的,所以每次合并是将k个节点合并成一个节点,

但是这样就会有一个问题:在合并是会出现一些空的节点

再推一下会发现:将n个节点合并成1个节点,每次将k个节点合并成一个节点,最终会有$(n-1) \ mod\ (k-1)$个节点单独空着无法合并

所以我们要补一些w为0的节点,若$(n-1) \ mod\ (k-1)!=0$,则需补充$(k-1)-(n-1)\ mod \ (k-1)$个节点

最终要求的高度就是所构造的哈夫曼树的高度

哈夫曼树的合并有点类似于NOIp2004石子合并,不过这里由于$n\leq10^5$于是可以直接用优先队列写过去

 1 #include<iostream>
 2 #include<string>
 3 #include<string.h>
 4 #include<stdio.h>
 5 #include<algorithm>
 6 #include<vector>
 7 #include<queue>
 8 #include<map>
 9 using namespace std;
10 struct node{
11     long long w;int h;
12     bool operator <(const node p)const
13     {
14         return ((w>p.w) || ((w==p.w) && (h>p.h)));
15     }
16 };
17 priority_queue<node> q;
18 int n,k;
19 int main()
20 {
21     scanf("%d%d",&n,&k);
22     int i;
23     for (i=1;i<=n;i++)
24     {
25         long long w;
26         scanf("%lld",&w);
27         q.push((node){w,1});
28     }
29     int cnt=0;
30     if ((n-1)%(k-1)!=0) cnt=((k-1)-((n-1)%(k-1)));
31     for (i=1;i<=cnt;i++) q.push((node){0,1});
32     long long ans=0;cnt+=n;
33     while (cnt>1)
34     {
35         long long sumw=0;int maxh=0;
36         for (i=1;i<=k;i++)
37         {
38             node p=q.top();q.pop();
39             sumw+=p.w;maxh=max(p.h,maxh);
40         }
41         q.push((node){sumw,maxh+1});cnt-=(k-1);
42         ans+=sumw;
43     }
44     printf("%lld\n",ans);
45     node p=q.top();
46     printf("%d\n",p.h-1);
47     return 0;
48 }

 

posted @ 2018-10-11 20:42  EncodeTalker  阅读(172)  评论(0编辑  收藏  举报