Zju 2334 monkey king [二项堆]解题报告

Zju 2334 monkey king [二项堆]解题报告

 http://andyzh1314.ycool.com/post.1050674.html

这题应用到了一个高级数据结构:二项堆。

我对二项堆做一个介绍:

它的名字中包含堆,顾名思义它包含了所有对所具有的功能,能在log n时间内求出堆中极值,也支持对某个节点作相应的修改,除此之外它还支持堆与堆之间的合并,这点给我们解决此问题提供了途径。聪明的读者已经想到了,我们让认识的猴子作为一个堆中的成员,打仗不过是我们要实现的堆与堆的合并。

说到这,你可能会产生疑问,既然这二项堆它支持可并优先队列,那么它究竟拥有的是什么样的结构呢?现在我来为你解答这个问题。

我们都知道二项式,了解二进制,我们来看一些例子:

1 (10  = 12);

2 (10)   = 10 (2);

4 (10  = 1002);

8 (10)   = 1000  (2);

……

       在二进制定义中,有这样一件奇妙的事情:1+1 = 10 10 + 10 = 100;…… 我们实质上是重述了二进制加法的定义。

       我们顺次把二进制的数位从右到左依次编号为12,……,p,…… 我们发现在第p位的两个1和在p+1位的一个1是等价的。

       了解了以上这些事,大家也就知道了什么是二项树。下面是明确的定义:

              二项树 Bk的定义是递归的定义的有序树。它有一个最大深度 k和堆的定义一样,也和上面我们说的二进制中的p一样。Bk 由两个二项树Bk-1 构成。设这两个树分别为BmBn 我们连接它们的方式就是Bm的左儿子这项的是BnBn的右兄弟指向的是原Bm的左儿子。这样我们看两个Bk-1和一个Bk就是等价的了。

那什么优势二项堆呢?

考虑:3 10= 112 = 1 + 10 510= 1012 = 1 + 100 ;……

我们如此定义二项堆:把若干个深度不同的二项树,按其深度从小到大连接起来形成队列,此队列称为二项堆。

如何做堆与堆的合并?简单说来我们先合并两个队列在合并队列中相邻的深度相同的点。

例如: 101 + 111 = 1 + 10 + 1 + 100 + 100

=1 + 1 + 10 + 100 + 100

= 10 + 10 + 100 + 100

= 100 + 100 + 100

=  ( 100 + 1000)

做题的目的是学习算法,而学习算法的最好途径还是匹配相应的习题。

下面我们结合程序来给出二项堆的一些基本结构:

这是结构:

Point= Longint;

Point 最为指针类型存在

pointtype=record

        father,leftchild,rightsub,setfather     :Point;

我们采取左儿子右兄弟的结构存储,并应用到了并查集setfather为节点的集合父亲。

        degree,power                            :Longint;

以此节点以下构成的子堆的深度,这个节点代表的猴子的强壮值。

end;

PROGRAM p2334;

CONST

        maxn=1000000;    // 最大范围

TYPE

        Point= Longint;

        pointtype=record

                father,leftchild,rightsub,setfather     :Point;

                degree,power                            :Longint;

        end;

VAR

        step                            :Integer;

        n,m                             :Longint;

        data                            :array[1..maxn]of Pointtype;

                            // 可并优先队列的表

PROCEDURE MakeNull;  // 初始化

var

        i                               :Longint;

begin

readln(n);

for i:=1 to n do

        with data[i] do

                begin

                father:=0;

                leftchild:=0;

                rightsub:=0;

                setfather:=0;

                degree:=0;

                readln(power);

                end;

end;

FUNCTION maxmember(a:point):Point;  // 求名称为a的二项堆中最大值所在的结点编号 我们肯定他一定在一系列二项树的根节点上 我们是通过a的右兄弟将其连接起来的

var

        max                             :Longint;

        p                               :Point;

begin

max:=-maxn;

repeat

        if data[a].power>max then

                begin

                max:=data[a].power;

                p:=a;

                end;

        if data[a].rightsub<>0 then a:=data[a].rightsub else break;

until false; // 枚举了每一个二项树 的到最大值所在点的编号p

maxmember:=p;

end;

FUNCTION Find(p:point):point; // 寻找p所在二项堆的根节点 并查结

var

        d,temp                          :Point;

begin

d:=p;

while (data[p].setfather<>0) do p:=data[p].Setfather;

while (d<>p) do   // 路径压缩

        begin

        temp:=data[d].Setfather;

        data[d].Setfather:=p;

        d:=temp;

        end;

Find:=p;

end;

PROCEDURE Link(p,q:Point); // p挂在q

begin

data[p].father:=q;

data[p].rightsub:=data[q].leftchild;

data[q].leftchild:=p;

data[q].degree:=data[p].degree+1;

end;

FUNCTION Merge(a,b:Point):point; // 按深度从小到大合并了两个队列 返回合并后的根节点名称

var

        p                               :Point;

begin

if data[a].degree>=data[b].degree then

        begin

        p:=b;

        b:=data[b].rightsub;

        end else

        begin

        p:=a;

        a:=data[a].rightsub;

        end;

Merge:=p;

while (a<>0)and(b<>0) do

        begin

        if data[a].degree>=data[b].degree then

                begin

                data[p].rightsub:=b;

                p:=b;

                b:=data[b].rightsub;

                end else

                begin

                data[p].rightsub:=a;

                p:=a;

                a:=data[a].rightsub;

                end;

        end;

if a<>0 then data[p].rightsub:=a;

if b<>0 then data[p].rightsub:=b;

end;

FUNCTION Union(a,b:Point):Point; 合并两个二项堆 名称为ab 返回合并后的名称

var

        h,p,prev,next                   :Point;

begin

h:=Merge(a,b);  // 合并队列

p:=h;          // 指针

prev:=0;       // p 的前导 指针

next:=data[p].rightsub; // p 的后继指针

while next<>0 do   // 没停止

        begin

        if (data[p].degree<>data[next].degree) or   // 两树深度不同

                ((data[next].rightsub<>0)and(data[data[next].rightsub].degree=data[p].degree)) then // 三树深度相同我们要合并后两者 所以掠过此点

                        begin

                        prev:=p;

                        p:=next;

                        end

                else if data[p].power>=data[next].power then  // 选择较小的p next挂在p

                        begin

                        data[p].rightsub:=data[next].rightsub;

                        link(next,p);

                        end

                else    // 选择较小的next p挂在next

                        begin

                        if prev=0 then h:=next

                                else data[prev].rightsub:=next;

                        link(p,next);

                        p:=next;

                        end;

        next:=data[p].rightsub;

        end;

Union:=h;

data[a].setfather:=h;

data[b].setfather:=h;

data[h].setfather:=0;    // 对集合的父亲做修改

end;

PROCEDURE Make(p:Point); // 调整二项树使之平衡

var

        temp                            :Longint;

        max,q,tp                        :Point;

begin

if p=0 then exit;

max:=-maxn;

tp:=data[p].leftchild;

q:=0;

while tp<>0 do

        begin

        if data[tp].power>max then

                begin

                max:=data[tp].power;

                q:=tp;

                end;

        tp:=data[tp].rightsub;

        end;

if (q<>0) and (data[p].power<data[q].power) then

        begin

        temp:=data[p].power;

        data[p].power:=data[q].power;

        data[q].power:=temp;

        Make(q);

        end;

end;

PROCEDURE Main;

var

        i,a,b,p,q,fa,fb,j              :Longint;

begin

readln(m);

for i:=1 to m do

        begin

        readln(a,b);

        fa:=find(a);

        fb:=find(b);

        if fa<>fb then // 两猴不认识

                begin

                p:=maxmember(fa);

                q:=maxmember(fb);

                data[p].power:=data[p].power div 2;

                data[q].power:=data[q].power div 2;

                make(p);

                make(q);

                writeln(data[maxmember(Union(fa,fb))].power);

                end else writeln(-1);

        end;

end;

BEGIN

step:=0;

while not eof do

        begin

        inc(step);

        MakeNull;

        Main;

        end;

END.

 

posted @ 2008-12-05 14:53  jesonpeng  阅读(530)  评论(1编辑  收藏  举报