合并果子

这是NOIP2004年提高组的一道题,随便搜索就能找到,VIJOS和RQNOJ都有。

这道题有很多很多种做法,首先最重要的是仔细读题,题中合并可以不按顺序,所以就不是石子归并的DP,直接贪心找最小即可。估计这道题当年坑了很多人。还有刚开始考虑时间复杂度,写得不一定对。。错了请指出来。

方法一,也就是最容易想到的,快排+插入排序。时间复杂度为O(n^2)。

program fruit;
var
    n,i:longint;
    a:array[1..10000] of longint;
    ans,temp,s:longint;
procedure qsort(left,right:longint);
var
    i,j,x,p:longint;
begin
    i:=left;
    j:=right;
    p:=i+random(j-i);
    x:=a[p];
    a[p]:=a[i];
    while i=x) do inc(i);
        a[j]:=a[i];
    end;
    a[i]:=x;
    if i>left then qsort(left,i-1);
    if i1 do
    begin
        a[n-1]:=a[n-1]+a[n];
        dec(n);
        s:=s+a[n];
        i:=n-1; //这个地方很容易错,不减1则无法进入循环。
        temp:=a[n];
        while (i>0) and (temp>a[i]) do
        begin
            a[i+1]:=a[i];
            dec(i);
        end;
        a[i+1]:=temp;
    end;
    writeln(s);
    close(output);
end.

方法二,堆排序,刚学的。时间复杂度为O(nlogn)

program heaptest;
var
    i,n,s,t:longint;
    a:array[1..10000] of longint;
    x,y:longint;
procedure shift(k:longint);
var
    i,x:longint;
begin
    x:=a[k];
    i:=2*k;
    while i<=n do
    begin
        if (i<n) and (a[i]>a[i+1]) then inc(i);
        if a[i]1 do
    begin
        x:=a[1];
        a[1]:=a[n];
        dec(n);
        shift(1);
        y:=a[1];
        s:=s+x+y;
        a[1]:=x+y;
        shift(1);
    end;
    writeln(s);
    close(input);
    close(output);
end.

方法三,构造huffman树,而且可以简略一些,最后循环输出权值即可。但是只能过几组数据,会超时。时间复杂度O(n^2)。

program fruitvhuffman;
type
    node=record
        parent:boolean;
        data:longint;
        end;
var
    a:array[1..19999] of node;
    i,j,k,n,ans:longint;
function min(h:longint):longint;
var
    m,i:longint;
begin
    m:=maxlongint;
    for i:=1 to h do
    if (a[i].parent=false) and (a[i].data<m) then
    begin
        m:=a[i].data;
        min:=i;
    end;
end;
begin
    assign(input,'fruit.in');
    assign(output,'fruit.out');
    reset(input);
    rewrite(output);
    fillchar(a,sizeof(a),0);
    readln(n);
    for i:=1 to n do read(a[i].data);
    readln;
    for k:=n+1 to 2*n-1 do
    begin
        i:=min(k-1);
        a[i].parent:=true;
        j:=min(k-1);
        a[j].parent:=true;
        a[k].data:=a[i].data+a[j].data;
    end;
    for i:=n+1 to 2*n-1 do inc(ans,a[i].data);
    writeln(ans);
    close(input);
    close(output);
end.

方法四,时间复杂度为O(n)。一下说明及程序均来源于Misty Sky (CC by-nc-sa)

其实,这道题目还有一种更简单的做法,考虑到每次合并两个结点以后,得到的新结点大小是递增的,于是可以维护两个队列,一个是原队列A,一个是新加入的队列B。每次就一定是在A或(和)B的首部取数,在B的尾部插入。

另外因为a[i]很小只有20000,所以可以使用基数排序,这样整体的时间复杂度就是O(n)。

var
  a,b,c : array[0..20000] of longint;
  la,ra,lb,rb,n,i,p : longint;
  sum : qword;
function get:longint;
begin
  if la>ra then begin inc(lb); exit(b[lb-1]); end;
  if lb>rb then begin inc(la); exit(a[la-1]); end;
  if a[la] < b[lb] then begin
	inc(la);
	exit(a[la-1]);
  end
  else begin
	inc(lb);
	exit(b[lb-1]);
  end;
end;
begin
  readln(n);
  for i := 1 to n do begin
	read(a[i]);
	inc(c[a[i]]);
  end;
  ra := 0;
  for i := 1 to 20000 do
	while not (c[i] = 0) do begin
	  inc(ra);
	  a[ra] := i;
	  dec(c[i]);
	end;
  la := 1;
  lb := 1; rb := 0;
  for i := 1 to n - 1 do begin
	p := get + get;
	inc(rb);
	b[rb] := p;
	inc(sum,p);
  end;
  writeln(sum);
end.

下面是用CENA的时间对比 ```

名称

排名

总得分

有效用时

array

1

100

0.16

heap

2

100

0.23

qsort+insert

3

100

1.97

randomqsort+insert

4

100

2.01

huffman

5

50

0.98

MS这篇不应该写,没什么意义。。就当是备份程序了。换了个显示程序的插件,美观些。
```


```

08.10.19 修改了一下heapsort的代码

comments powered by Disqus