Zju 2334 monkey king [二项堆]解题报告
Zju 2334 monkey king [二项堆]解题报告
http://andyzh1314.ycool.com/post.1050674.html
这题应用到了一个高级数据结构:二项堆。
我对二项堆做一个介绍:
它的名字中包含堆,顾名思义它包含了所有对所具有的功能,能在log n时间内求出堆中极值,也支持对某个节点作相应的修改,除此之外它还支持堆与堆之间的合并,这点给我们解决此问题提供了途径。聪明的读者已经想到了,我们让认识的猴子作为一个堆中的成员,打仗不过是我们要实现的堆与堆的合并。
说到这,你可能会产生疑问,既然这二项堆它支持可并优先队列,那么它究竟拥有的是什么样的结构呢?现在我来为你解答这个问题。
我们都知道二项式,了解二进制,我们来看一些例子:
1 (10) = 1(2);
2 (10) = 10 (2);
4 (10) = 100(2);
8 (10) = 1000 (2);
……
在二进制定义中,有这样一件奇妙的事情:1+1 = 10; 10 + 10 = 100;…… 我们实质上是重述了二进制加法的定义。
我们顺次把二进制的数位从右到左依次编号为1,2,……,p,…… 我们发现在第p位的两个1和在p+1位的一个1是等价的。
了解了以上这些事,大家也就知道了什么是二项树。下面是明确的定义:
二项树 Bk的定义是递归的定义的有序树。它有一个最大深度 k和堆的定义一样,也和上面我们说的二进制中的p一样。Bk 由两个二项树Bk-1 构成。设这两个树分别为Bm,Bn, 我们连接它们的方式就是Bm的左儿子这项的是Bn,Bn的右兄弟指向的是原Bm的左儿子。这样我们看两个Bk-1和一个Bk就是等价的了。
那什么优势二项堆呢?
考虑:3 (10)= 11(2) = 1 + 10 ; 5(10)= 101(2) = 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; 合并两个二项堆 名称为a,b 返回合并后的名称
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.