题目链接: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 }