如果只是单纯地每次切出一个最大的最终小木块,那么显然不对。比如上图中,如果从39里面切除20之后,得到19,如果只是切除最大小木块即5,那么得到14与5,后面对14进行切除,在切除5、5、4,共需要(39+19+14+9),如果按照哈弗曼切除,则为(39+19+10+9),显然后者要小。因此简单地切除当前最大块是不对的。
从上面的例子也可以看出,要保证越是大的木块,应该在哈弗曼树中的层数越少,即今早把它区分开来,否则停留在原始大块里增加切除负担。实际上,为了总和最小,的确应该每次找出最大的,但不是简单地只是找出一个,而考虑到对于一个最终小木块来说,每个小木块一定是由一个较大木块分成两部分得到的其中一部分,因此用哈弗曼树保证总和最小(哈弗曼树本质就是保证二叉树的权值和最小。)
#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;
int main(){
int n;
int nCase;
scanf("%d",&nCase);
for(int i=0;i<nCase;i++){
scanf("%d",&n);
priority_queue<long long,vector<long long>,greater<long long> > qu;
while(n-->0){
long long x;
scanf("%lld",&x);
qu.push(x);
}
long long a=0,b=0;
long long res=0;
if(qu.size()==1)res=qu.top();//由于最底层的顶点(原始序列)都加进来了,因此最顶层顶点不用考虑了(前者所有原始序列之和即为顶层顶点数据。)
while(1){
a=qu.top();
qu.pop();
if(qu.empty())break;
b=qu.top();
qu.pop();
res+=a+b;
qu.push(a+b) ;
}
printf("%lld\n",res);
if(i!=nCase-1)printf("\n");
}
return 0;
}