洛谷P2168荷马史诗(小根堆)

     首先这个题很容易想到哈夫曼树,但哈夫曼是二进制编码的,而本题为k进制编码,因此也叫作k叉二叉树。既然想到这里,那类似二进制哈夫曼树的构造,我们先构造一个优先队列,(优先队列是个好东西,因为他可以自动排队啊,这里我们按小根堆,也就是队列中的元素从小到大排序),我们把前k个最小的数弹出来,组成一组,再把他们的和加入优先队列。现在就有一个问题,就是,你叶子那些层都填好了,填的满满的,但是,你最后一层,也就是根节点的下一层还有空,那这可就很浪费资源了,因为根节点的下一层的编码长度为1,你要是把一个叶子结点放了这个地方,那最后的编码长度一定更小啊。

    所以这里就有一个很巧妙的方法,就是用0来顶,一直往优先队列里加0,直到根节点的下一层都填满。我们可以知道,一棵完整的哈夫曼树,只要不是叶子结点,每个节点都有k个孩子,那这棵哈夫曼树所有叶子结点总共有多少个呢?

   以上图为例,此时k取3,要是2没有分支,那该树有3个叶子节点,但是,一旦有了分支,就会发现,多了两个,成了5个叶子结点了,所以,要想满哈夫曼树,那他的叶子结点个数一定为x*(k-1)+1。又因为我们已知有n个叶子结点,所以我们也就通过判断(n-1)%(k-1)来判断是不是满哈夫曼树,不满的话,用0去往上顶空,那需要顶多少个空呢,可以推算的要顶(k-1)-(n-1)%(k-1)个空。(很简单嘛,他不是不整除嘛,就是余数小于k-1,那把余数加到k-1不就行了嘛)。

好了,现在第一问结束,在一个问题是,那个层次,怎么算是最大。其实从队列里出来k个数相加后,算出来的那个数,就相当于在这k个数的基础上往上跳了一层,当然,这k个数层数可能不一样,那最后的和一定是最大那一层加一啊,所以我们只要求出最大一层再加一就行。

现在就上代码了

#include<bits/stdc++.h>
using namespace std;
struct node{
	long long w,h;
	node(){w=0;h=0;}
	node(long long w,long long h):w(w),h(h){}
	bool operator < (const node &a) const{return a.w==w?h>a.h:w>a.w;}
	//优先队列默认大根堆,既堆顶元素最大,
	//默认的bool operator() (const T& x, const T& y) const {return x<y;} 
	//要改成栈顶元素最小,需要重载比较内部函数< 
};
priority_queue<node>q;
long long n,k,ans,sum=0,s1,a,layer=0,h;
int main(){
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a);
		q.push(node(a,1));
		sum+=a;
	}
	if(n<=k) {ans=sum;layer=1;}
	else{
	 if((n-1)%(k-1)!=0)
	 {
	 	for(int i=1;i<=((k-1)-(n-1)%(k-1));i++)
	 	q.push(node(0,1));//非常重要,就是k叉二叉树最后一层可能不满,用0来代替 
	 }
	 while(q.size()>=k){
	 	s1=0;h=-1;
	 	for(int i=1;i<=k;i++)
	 	 {
	 		node x=q.top();q.pop();s1+=x.w;
	 		h=max(h,x.h);
		 }
		 q.push(node(s1,h+1));
		 ans+=s1;
	 } 
	}
	printf("%lld\n%lld",ans,q.top().h-1);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值