记录编号 298360 评测结果 AAAAAAAAAA
题目名称 [NOIP 2004]合并果子 最终得分 100
用户昵称 Gravatarzeppoe 是否通过 通过
代码语言 Pascal 运行时间 0.049 s
提交时间 2016-08-20 20:19:29 内存使用 0.20 MiB
显示代码纯文本
const maxn=10000;
type arrtype=array[1..maxn] of longint;
var
  r:arrtype;
  i,n:integer;
  total,temp:longint;

procedure heap(n,ii:integer);//建立最小数堆的过程
  //跟每个输进来的数的孩子们比较和交换,直到输进来的数原来的位置上填上这个小分堆的最小值,爆出过程,下面的for循环是向上找的总趋势
  var x:longint; i,j:integer;
  begin
    i:=ii;
    x:=r[ii];
    j:=2*ii;
    while j<=n do
      begin
        if (j<n) and (r[j]>r[j+1]) then j:=j+1;//如果ii的左孩子比右孩子大,那么和右孩子换,为了保证每次和最小的换
        if x>r[j] then begin r[i]:=r[j];i:=j;j:=2*i end
          //如果ii比左右孩子中最小的大,那么就把这个位置填成最小的孩子,由最小孩子原来的位置接着往下搜,同时更新j为新节点的左孩子下标
          //在while循环中,可以一直推下去,直到不用换
          else j:=n+1;
          //若不用换就强行爆出循环,继续下面for循环的建立堆的过程
      end;
    r[i]:=x;//搜到最后的孩子变成原数,堆建立
  end;
begin
assign(input,'fruit.in');
assign(output,'fruit.out');
reset(input);
rewrite(output);
  readln(n);
  for i:=1 to n do read(r[i]);
  for i:=n div 2 downto 1 do heap(n,i); {建立小根堆}
  //从n/2 处开始查找
  //不断向上走,比较,交换
  total:=0;
  temp:=r[1];r[1]:=r[n];r[n]:=temp; 	{把堆顶最小数和堆中最后一个数交换}
  heap(n-1,1);  			{调整堆,剩下N-1个数中再选出一个最小数}
  //以上为弹出堆中最小根,再将剩下的数重建最小堆的过程
  for i:=n-1 downto 2 do
    begin
      r[1]:=r[1]+r[i+1];  		{当前堆中的两个最小数合并,存入堆顶,变成新的堆顶}
      total:=total+r[1];//加和,求最小体力消耗,就是每次最小两数加和的总和
      heap(i,1);  			{选出当前堆中最小数}
      //改过堆顶后重排
      temp:=r[1];r[1]:=r[i];r[i]:=temp;
      heap(i-1,1); 			{再选一个最小数}
     //重复弹出最小堆,重建的过程
    end;
  writeln(total+r[1]+r[2]) 		{加上最后一次的合并,输出结果};
close(input);
close(output);
end.
{
堆排序
堆的概念:
对一组待排序的关键码,首先把它们按堆的定义排列成一个序列(称为建堆),这就找到了最小的关键码,然后将最小的关键码取出,用剩下的关键码再建堆,便得到次最小的关键码,如此反复进行,直到将全部关键码排好序为止。
根结点是堆中的最小元素。

从一个无序列建成一个堆的过程就是一个反复“筛选”的过程。
若将此序列看成是一个完全二叉树,[i为根,初始值为一;2*i看作左孩子;2*i+1看作右孩子,把i更新为左右节点,继续建树,到没有数]
则最后一个非终端结点是第[n/2]个元素,由此“筛选”只需从第[n/2]个元素开始
筛选过程就是,从n/2到1 ,每次往下换直到分堆根节点最小,堆建立}