[PKU 3481][Noi 2004 Cashier]伸展树Splay & 平衡树SBT(上)

{

本文主要介绍一下伸展树与平衡树SBT

平衡树应用广泛 效率极高(最坏为Logn)

是实现优先队列 数据字典的不二选择

伸展树因其独有的Splay操作

可以应对很多线段树难以处理的区间问题

而不仅仅是用作一棵排序二叉树来处理数据

而且伸展树效率也很高 达到了均摊Logn的级别

}

先讲平衡树SBT

CQF大神的SBT我已经膜拜好久了

程序也差不多看懂了

果然是大神的程序 很精简

有些地方在论文里没有解释

在这里讲一下

先是旋转(zig|zag)

 

1 procedure right_rotate(var t:longint);//右旋 使用变量参数 方便操作
2  begin
3 k:=left[t];
4 left[t]:=right[k];
5 right[k]:=t;
6 s[k]:=s[t];
7 s[t]:=s[left[t]]+s[right[t]]+1;
8 t:=k;//根节点改变 更新变参
9  end;
1 procedure left_rotate(var t:longint);//左旋
2  begin
3 k:=right[t];
4 right[t]:=left[k];
5 left[k]:=t;
6 s[k]:=s[t];
7 s[t]:=s[left[t]]+s[right[t]]+1;
8 t:=k;
9  end;

 

由于两个旋转是对称的 就单讲一个右旋

前三句话很好理解 是旋转需要的指针变动

第6行更新新的根节点k的size域 就是原来根节点t的size

第7行更新t节点的size域 此时儿子已经改变且size域已经求好 所以通过儿子计算即可

最后一行比较难理解{t:=k;}

这里因为t是传的变量参数 相当于是传递的记录当前子树的根的变量

于是k旋转上去之后 子树的根也应当自动更新

(空讲不一定清楚 在后文讨论其他函数的时候会再次具体说明)

下来是插入(Insert)

 

1 procedure insert(var t,v:longint);//同样使用变参
2  begin
3 if t=0 then begin
4 inc(tt);
5 t:=tt;//使用变参之后 节点父亲的儿子指针也会同时改变
6 key[t]:=v;
7 s[t]:=1;
8 left[t]:=0;
9 right[t]:=0; //到达边界 新建节点
10 end
11 else begin
12 inc(s[t]);//注意更新size
13 if v<key[t] then
14 insert(left[t],v)
15 else
16 insert(right[t],v);
17 maintain(t,v>=key[t]);
18 end;
19  end;

第3行 如果当前子树为空树则新建节点为其根

 

其中有一句{t:=tt;}这一句是有用的

我们看到t是传递的变量 这个变量是什么呢?

递归退回到上一层 我们看到是l[x]或r[x]

这里改变t为tt 事实上就是改变l[x]或r[x] 这里也就是传递变参的高明之处

不用记录父亲 直接可以改变父亲节点的儿子指针

这样代码便会简洁许多

回到上面旋转留下的问题

由于插入以及后面的删除 maintain函数都使用了变参

旋转作为最基础的操作也应当使用变参

事实上 利用变参不仅可以传递值 还传递指针的特性

可以使很多代码得以简化 但是对思考的细致有要求

下来考虑maintain函数

 

1 procedure maintain(var t:longint;flag:boolean);//保持SBT性质
2  begin
3 if flag=false then//用boolean变量flag决定调整左子树还是右子树
4 if s[left[left[t]]]>s[right[t]] then
5 right_rotate(t)
6 else
7 if s[right[left[t]]]>s[right[t]] then begin
8 left_rotate(left[t]);
9 right_rotate(t);
10 end
11 else
12 exit
13 else
14 if s[right[right[t]]]>s[left[t]] then
15 left_rotate(t)
16 else
17 if s[left[right[t]]]>s[left[t]] then begin
18 right_rotate(right[t]);
19 left_rotate(t);
20 end//此处各种情况在论文里有详细说明
21 else
22 exit;//无需调整 直接退出
23 maintain(left[t],false);//递归调整性质可能被破坏的子树
24 maintain(right[t],true);//优化:只调整左子树的左子树和右子树的右子树
25 maintain(t,true);
26 maintain(t,false);//调整自身
27  end;

maintain在论文里讲的很详细 不再细说

 

这个函数能达到O(1)的复杂度

(膜拜CQF大神 怎么想到的)

下面是删除(Delete)

 

1 function delete(var t:longint;v:longint):longint;
  //
这里的删除必定会删掉一个节点并返回这个节点
2  begin
3 dec(s[t]);
4 if (v=key[t])or(v<key[t])and(left[t]=0)or(v>key[t])and(right[t]=0) then begin
5 delete:=key[t];//到达边界 删除一个节点 返回值
6 if (left[t]=0)or(right[t]=0) then
7 t:=left[t]+right[t]-0//直接删除
8 else
9 key[t]:=delete(left[t],key[t]+1);//从右子树中取下一个最大的节点取代当前节点
10 end
11 else
12 if v<key[t] then
13 delete:=delete(left[t],v)
14 else
15 delete:=delete(right[t],v);
16  end;//这里不用maintain 因为删除不增加高度

CQF的删除我一开始也没看懂

 

我一直在想删除一定会删掉一个节点 不会出错么

事实上这种"暴力"的删法相当巧妙

至于怕删错 用下面的find函数特判即可

第一句话 由于必定删除一个节点 不由分说 讲这个子树的size减1

    我们看到 如果不是必定删除一个的话 size值减不减一还是个麻烦的事

第2行:有几种情况是必须在这里把根节点砍掉的

  比如要删的值小于当前节点而左子树为空

  要删的值大于当前节点而右子树为空

  抑或是当前节点是要删的节点

我们注意到这样我们删的节点的值必然是最接近于我们想删的值的

下面分情况讨论怎么删

  一是只有一个子树 直接把儿子连到父亲即可

  二是由2个子树 就从左子树挖一个最大的填到当前子树根上

    根据上面最接近于我们想删的值的结论

    所以递归调用删除key[x]+1必然是删的左子树最大节点

第11行 不符合上面任何一个情况 递归删除

最后由于删除不增加高度 maintain也可以省了

下面是各个查询函数(Find Rank Select Pred Succ)

 

1 function find(var t,v:longint):boolean;//以下可以不用变参 因为是查询操作
2  begin
3 if t=0 then
4 exit(false);//没有查询到
5 if v<key[t] then
6 find:=find(left[t],v)
7 else
8 find:=(key[t]=v)or find(right[t],v);//递归
9  end;
10  function rank(var t,v:longint):longint;
11  begin
12 if t=0 then
13 exit(1);//子树为空时 所查节点排位自然为1
14 if v<=key[t] then
15 rank:=rank(left[t],v)
16 else
17 rank:=s[left[t]]+1+rank(right[t],v);//向右子树查询 注意右子树中的排位不是真是排位
18  end;
19  function select(var t:longint;k:longint):longint;
20  begin
21 if k=s[left[t]]+1 then
22 exit(key[t]);//查询节点为根
23 if k<=s[left[t]] then
24 select:=select(left[t],k)
25 else
26 select:=select(right[t],k-1-s[left[t]]);//向右子树查询 注意同rank
27  end;
28  function pred(var t,v:longint):longint;
29  begin
30 if t=0 then
31 exit(v);//到达边界 即 所查节点没有前驱直接返回所查节点
32 if v<=key[t] then
33 pred:=pred(left[t],v)//前驱必在左子树上
34 else begin
35 pred:=pred(right[t],v);
36 if pred=v then//在右子树上没有前驱 即 所查节点为右子树上最小
37 pred:=key[t];//前驱为根
38 end;
39  end;
40  function succ(var t,v:longint):longint;
41  begin
42 if t=0 then
43 exit(v);
44 if key[t]<=v then
45 succ:=succ(right[t],v)
46 else begin
47 succ:=succ(left[t],v);
48 if succ=v then
49 succ:=key[t];//后继和查前驱类似
50 end;
51  end;

注释比较清楚 就不细写了

 

需要注意查询的边界条件比如Rank的边界:空树中返回1

实质上Rank是将所查节点假设插入后的rank值返回

比如{1,2,4,5}中 6的rank是5,0的rank是1,3的rank是3,4的rank是3

自然而然 在一个空集{}中任何数的rank都是1

succ和pred在遇到空树时返回v

也是假设插入后取前驱后继

当为最大最小值时直接返回最大最小值

比如{1,2,4,5}中 6的pred是5,0的pred是0,3的pred是2,4的pred是2

所以{}中 任何数的前驱后继是其本身

最后是主函数(main)

 

1 begin
2 tt:=0;//总节点数
3 t:=0;//用t纪录数组中的当前根节点 根节点作为变参传递时 根据需要会随时改变(向前移动)
4 s[0]:=0;//空点初始size为0
5 for q:=1 to q do
6 case a[q] of//由于删除等操作必定删除一个节点或返回值 有必要时应先确定所查节点是否在树中
7 1:insert(t,b[q]);
8 2:if find(t,b[q]) then delete(t,b[q]);
9 3:writeln(find(t,b[q]));
10 4:writeln(rank(t,b[q]));
11 5:writeln(select(t,b[q]));
12 6:writeln(pred(t,b[q]));
13 7:writeln(succ(t,b[q]));
14 end;
15 end;
16 begin
17 assign(input,'input.txt');
18 assign(output,'ans0.txt');
19 reset(input);
20 rewrite(output);
21 init;
22 work;
23 close(input);
24 close(output);
25 end.

注释清楚 注意s[0]ttt的含义

 

最后给出完整的陈启峰官方SBT代码

 

CQF_SBT
1 //http://www.nocow.cn/index.php/SBT#.E6.80.A7.E8.83.BD.E5.88.86.E6.9E.90
2 //cqf大神的官方源码
3 //注释写得不一定好 但是是我看了很长时间代码+论文才理解的
4 program CQF_SBT;
5 const maxn=2000000;//n适当大点 因为整棵树会在数组里向前移动
6 var key,s,left,right,a,b:array[0..maxn] of longint;
7 tt,q:longint;
8 procedure init;
9 begin
10 readln(q);
11 for q:=1 to q do
12 readln(a[q],b[q]);
13 end;
14 procedure work;
15 var t,k:longint;
16 procedure right_rotate(var t:longint);//右旋 使用变量参数 方便操作
17 begin
18 k:=left[t];
19 left[t]:=right[k];
20 right[k]:=t;
21 s[k]:=s[t];
22 s[t]:=s[left[t]]+s[right[t]]+1;
23 t:=k;//根节点改变 更新变参
24 end;
25 procedure left_rotate(var t:longint);//左旋
26 begin
27 k:=right[t];
28 right[t]:=left[k];
29 left[k]:=t;
30 s[k]:=s[t];
31 s[t]:=s[left[t]]+s[right[t]]+1;
32 t:=k;
33 end;
34 procedure maintain(var t:longint;flag:boolean);//保持SBT性质
35 begin
36 if flag=false then//用boolean变量flag决定调整左子树还是右子树
37 if s[left[left[t]]]>s[right[t]] then
38 right_rotate(t)
39 else
40 if s[right[left[t]]]>s[right[t]] then begin
41 left_rotate(left[t]);
42 right_rotate(t);
43 end
44 else
45 exit
46 else
47 if s[right[right[t]]]>s[left[t]] then
48 left_rotate(t)
49 else
50 if s[left[right[t]]]>s[left[t]] then begin
51 right_rotate(right[t]);
52 left_rotate(t);
53 end//此处各种情况在论文里有详细说明
54 else
55 exit;//无需调整 直接退出
56 maintain(left[t],false);//递归调整性质可能被破坏的子树
57 maintain(right[t],true);//优化:只调整左子树的左子树和右子树的右子树
58 maintain(t,true);
59 maintain(t,false);//调整自身
60 end;
61 procedure insert(var t,v:longint);//同样使用变参
62 begin
63 if t=0 then begin
64 inc(tt);
65 t:=tt;//使用变参之后 节点父亲的儿子指针也会同时改变
66 key[t]:=v;
67 s[t]:=1;
68 left[t]:=0;
69 right[t]:=0; //到达边界 新建节点
70 end
71 else begin
72 inc(s[t]);//注意更新size
73 if v<key[t] then
74 insert(left[t],v)
75 else
76 insert(right[t],v);
77 maintain(t,v>=key[t]);
78 end;
79 end;
80 function delete(var t:longint;v:longint):longint;//这里的删除必定会删掉一个节点并返回这个节点
81 begin
82 dec(s[t]);
83 if (v=key[t])or(v<key[t])and(left[t]=0)or(v>key[t])and(right[t]=0) then begin
84 delete:=key[t];//到达边界 删除一个节点 返回值
85 if (left[t]=0)or(right[t]=0) then
86 t:=left[t]+right[t]-0//直接删除
87 else
88 key[t]:=delete(left[t],key[t]+1);//从右子树中取下一个最大的节点取代当前节点
89 end
90 else
91 if v<key[t] then
92 delete:=delete(left[t],v)
93 else
94 delete:=delete(right[t],v);
95 end;//这里不用maintain 因为删除不增加高度
96 function find(var t,v:longint):boolean;//以下可以不用变参 因为是查询操作
97 begin
98 if t=0 then
99 exit(false);//没有查询到
100 if v<key[t] then
101 find:=find(left[t],v)
102 else
103 find:=(key[t]=v)or find(right[t],v);//递归
104 end;
105 function rank(var t,v:longint):longint;
106 begin
107 if t=0 then
108 exit(1);//子树为空时 所查节点排位自然为1
109 if v<=key[t] then
110 rank:=rank(left[t],v)
111 else
112 rank:=s[left[t]]+1+rank(right[t],v);//向右子树查询 注意右子树中的排位不是真是排位
113 end;
114 function select(var t:longint;k:longint):longint;
115 begin
116 if k=s[left[t]]+1 then
117 exit(key[t]);//查询节点为根
118 if k<=s[left[t]] then
119 select:=select(left[t],k)
120 else
121 select:=select(right[t],k-1-s[left[t]]);//向右子树查询 注意同rank
122 end;
123 function pred(var t,v:longint):longint;
124 begin
125 if t=0 then
126 exit(v);//到达边界 即 所查节点没有前驱直接返回所查节点
127 if v<=key[t] then
128 pred:=pred(left[t],v)//前驱必在左子树上
129 else begin
130 pred:=pred(right[t],v);
131 if pred=v then//在右子树上没有前驱 即 所查节点为右子树上最小
132 pred:=key[t];//前驱为根
133 end;
134 end;
135 function succ(var t,v:longint):longint;
136 begin
137 if t=0 then
138 exit(v);
139 if key[t]<=v then
140 succ:=succ(right[t],v)
141 else begin
142 succ:=succ(left[t],v);
143 if succ=v then
144 succ:=key[t];//后继和查前驱类似
145 end;
146 end;
147 begin
148 tt:=0;//总节点数
149 t:=0;//用t纪录数组中的当前根节点 根节点作为变参传递时 根据需要会随时改变(向前移动)
150 s[0]:=0;//空点初始size为0
151 for q:=1 to q do
152 case a[q] of//由于删除等操作必定删除一个节点或返回值 有必要时应先确定所查节点是否在树中
153 1:insert(t,b[q]);
154 2:if find(t,b[q]) then delete(t,b[q]);
155 3:writeln(find(t,b[q]));
156 4:writeln(rank(t,b[q]));
157 5:writeln(select(t,b[q]));
158 6:writeln(pred(t,b[q]));
159 7:writeln(succ(t,b[q]));
160 end;
161 end;
162 begin
163 assign(input,'input.txt');
164 assign(output,'ans0.txt');
165 reset(input);
166 rewrite(output);
167 init;
168 work;
169 close(input);
170 close(output);
171 end.

 

Noi2004郁闷的出纳员是可以运用SBT解决的

只需注意工资是整体上浮下调的

我们累加工资的调整代数和记录差值delta

比如工资涨了5 又降了7 delta=-2

当前工资底线需要时时改变 总工资涨10 底线就要扣10 而员工工资不需整体改变

比如原来工资底线为8 现在要按8-(-2)=10来算

而对于新来的员工 出始工资统一减delta即可

还要注意要删除的是一棵一棵的子树 不要一个一个去删

代码如下

 

Cashier
1 const maxn=200000;
2 var l,r,s,n:array[0..maxn]of longint;
3 k,i,tt,ans,t,d,m,min:longint;
4 ch:char;
5 procedure zig(var x:longint);
6 var y:longint;
7 begin
8 y:=l[x]; l[x]:=r[y]; r[y]:=x;
9 s[y]:=s[x]; s[x]:=s[l[x]]+s[r[x]]+1;
10 x:=y;
11 end;
12 procedure zag(var x:longint);
13 var y:longint;
14 begin
15 y:=r[x]; r[x]:=l[y]; l[y]:=x;
16 s[y]:=s[x]; s[x]:=s[l[x]]+s[r[x]]+1;
17 x:=y;
18 end;
19 procedure maintain(var x:longint; flag:boolean);
20 begin
21 if flag
22 then begin
23 if s[l[l[x]]]>s[r[x]] then zig(x)
24 else if s[l[r[x]]]>s[r[x]]
25 then begin zag(l[x]); zig(x); end
26 else exit;
27 end
28 else begin
29 if s[r[r[x]]]>s[l[x]] then zag(x)
30 else if s[r[l[x]]]>s[l[x]]
31 then begin zig(r[x]); zag(x); end
32 else exit;
33 end;
34 maintain(l[x],true); maintain(r[x],false);
35 maintain(x,true);maintain(x,false);
36 end;
37 procedure insert(var x:longint; v:longint);
38 begin
39 if x=0
40 then begin
41 inc(tt); x:=tt;
42 n[x]:=v; s[x]:=1;
43 end
44 else begin
45 inc(s[x]);
46 if v<n[x] then insert(l[x],v)
47 else insert(r[x],v);
48 maintain(x,v<=n[x]);
49 end;
50 end;
51 function delete(var x:longint):longint;
52 var y:longint;
53 begin
54 if x=0 then exit(0);
55 if n[x]<min
56 then begin
57 y:=s[l[x]]+1; l[x]:=0;
58 y:=y+delete(r[x]);
59 s[x]:=s[x]-y;
60 s[r[x]]:=s[x]; x:=r[x];
61 end
62 else begin
63 y:=delete(l[x]);
64 s[x]:=s[x]-y;
65 end;
66 delete:=y;
67 end;
68 function select(x,k:longint):longint;
69 begin
70 if k=s[l[x]]+1 then exit(n[x]+d);
71 if k<s[l[x]]+1 then exit(select(l[x],k))
72 else exit(select(r[x],k-s[l[x]]-1));
73 end;
74 begin
75 assign(input,'cashier.in'); reset(input);
76 assign(output,'cashier.out'); rewrite(output);
77 d:=0; t:=0; tt:=0; s[0]:=0;
78 readln(m,min); ans:=0;
79 for i:=1 to m do
80 begin
81 readln(ch,k);
82 case ch of
83 'I': begin
84 ans:=ans+delete(t);
85 if k-d>=min then insert(t,k-d);
86 end;
87 'A': begin d:=d+k; min:=min-k; end;
88 'S': begin d:=d-k; min:=min+k; ans:=ans+delete(t); end;
89 'F': begin
90 ans:=ans+delete(t);
91 if s[t]<k then writeln(-1)
92 else writeln(select(t,s[t]-k+1));
93 end;
94 end;
95 end;
96 writeln(ans);
97 close(input); close(output);
98 end.
99

 

PKU3481是平衡树的裸题

伸展树有时候可以作为一个平衡树的替代

这题没有恶心数据 伸展树很快

给个伸展树代码吧 也算承上启下了

 

dQueue_Splay
1 const maxn=300000;
2 var m,n,l,r,f:array[0..maxn]of longint;
3 root,tt,k,p,q:longint;
4 procedure zig(var x:longint);
5 var y:longint;
6 begin
7 y:=l[x]; l[x]:=r[y]; r[y]:=x;
8 f[y]:=f[x]; f[x]:=y;
9 if l[x]<>0 then f[l[x]]:=x;
10 x:=y;
11 end;
12 procedure zag(var x:longint);
13 var y:longint;
14 begin
15 y:=r[x]; r[x]:=l[y]; l[y]:=x;
16 f[y]:=f[x]; f[x]:=y;
17 if r[x]<>0 then f[r[x]]:=x;
18 x:=y;
19 end;
20 procedure splay(var x:longint);
21 begin
22 while f[x]<>0 do
23 if l[f[x]]=x
24 then begin
25 if f[f[x]]=0
26 then zig(root)
27 else if l[f[f[x]]]=f[x]
28 then zig(l[f[f[x]]])
29 else zig(r[f[f[x]]]);
30 end
31 else begin
32 if f[f[x]]=0
33 then zag(root)
34 else if l[f[f[x]]]=f[x]
35 then zag(l[f[f[x]]])
36 else zag(r[f[f[x]]]);
37 end;
38 end;
39 procedure insert(var x:longint; k,p:longint);
40 var i,j:longint;
41 begin
42 if x=0
43 then begin
44 inc(tt); x:=tt;
45 n[x]:=p; m[x]:=k;
46 exit;
47 end;
48 j:=x;
49 while j<>0 do
50 begin
51 i:=j;
52 if p<n[i] then j:=l[i] else j:=r[i];
53 end;
54 inc(tt); j:=tt;
55 n[j]:=p; m[j]:=k;
56 f[j]:=i; l[j]:=0; r[j]:=0;
57 if p<n[i] then l[i]:=j else r[i]:=j;
58 splay(j);
59 end;
60 procedure deletemin(var x:longint);
61 var i:longint;
62 begin
63 if x=0
64 then begin writeln(0); exit; end;
65 i:=x;
66 while l[i]<>0 do i:=l[i];
67 writeln(m[i]);
68 splay(i);
69 x:=r[i]; f[r[i]]:=0;
70 end;
71 procedure deletemax(var x:longint);
72 var i:longint;
73 begin
74 if x=0
75 then begin writeln(0); exit; end;
76 i:=x;
77 while r[i]<>0 do i:=r[i];
78 writeln(m[i]);
79 splay(i);
80 x:=l[i]; f[l[i]]:=0;
81 end;
82 begin
83 assign(input,'dq.in'); reset(input);
84 assign(output,'dq.out'); rewrite(output);
85 read(q);
86 tt:=0; root:=0;
87 while q<>0 do
88 begin
89 case q of
90 1: begin
91 readln(k,p);
92 insert(root,k,p);
93 end;
94 2: deletemax(root);
95 3: deletemin(root);
96 end;
97 read(q);
98 end;
99 close(input); close(output);
100 end.
101

 

 

在第二部分里我们介绍伸展树(Splay Tree)

先贴一下我自己写的伸展树代码 不长 也比较快

(我只用了单旋来Splay 但是好像不比双旋慢)

 

Splay Tree
1 const maxn=2000000;
2 var l,r,f,n:array[0..maxn]of longint;
3 order,root,m,i,k,tt,t:longint;
4 procedure zig(var x:longint);
5 var y:longint;
6 begin
7 y:=l[x]; l[x]:=r[y]; r[y]:=x;
8 f[y]:=f[x]; f[x]:=y;
9 if l[x]<>0 then f[l[x]]:=x;
10 x:=y;
11 end;
12 procedure zag(var x:longint);
13 var y:longint;
14 begin
15 y:=r[x]; r[x]:=l[y]; l[y]:=x;
16 f[y]:=f[x]; f[x]:=y;
17 if r[x]<>0 then f[r[x]]:=x;
18 x:=y;
19 end;
20 procedure splay(var x,S:longint);
21 begin
22 while f[x]<>f[S] do
23 if l[f[x]]=x
24 then begin
25 if f[x]=S
26 then zig(S)
27 else if l[f[f[x]]]=f[x]
28 then zig(l[f[f[x]]])
29 else zig(r[f[f[x]]]);
30 end
31 else begin
32 if f[x]=S
33 then zag(S)
34 else if l[f[f[x]]]=f[x]
35 then zag(l[f[f[x]]])
36 else zag(r[f[f[x]]]);
37 end;
38 end;
39 function find(x,v:longint):boolean;
40 begin
41 if x=0 then exit(false);
42 if v=n[x] then exit(true);
43 if v<n[x]
44 then find:=find(l[x],v)
45 else find:=find(r[x],v);
46 end;
47 procedure insert(var x:longint; v:longint);
48 var i,j:longint;
49 begin
50 if x=0
51 then begin
52 inc(tt); x:=tt;
53 l[x]:=0; r[x]:=0; f[x]:=0;
54 n[x]:=v;
55 exit; end;
56 j:=x;
57 while j<>0 do
58 begin
59 i:=j;
60 if v<=n[i] then j:=l[i] else j:=r[i];
61 end;
62 inc(tt); j:=tt;
63 if v<=n[i] then l[i]:=j else r[i]:=j;
64 f[j]:=i; n[j]:=v;
65 splay(j,root);
66 end;
67 procedure join(var x,y:longint);
68 var i,j:longint;
69 begin
70 if (x=0)or(y=0)
71 then begin
72 x:=x+y;
73 exit; end;
74 i:=x; j:=y;
75 while r[i]<>0 do i:=r[i];
76 while l[j]<>0 do j:=l[j];
77 splay(i,x); splay(j,y);
78 r[i]:=j; f[j]:=i;
79 x:=i;
80 end;
81 procedure delete(var x:longint; v:longint);
82 var i,j:longint;
83 begin
84 i:=x;
85 while (i<>0)and(n[i]<>v) do
86 if v<n[i] then i:=l[i] else i:=r[i];
87 if i=0 then exit;
88 splay(i,root);
89 join(l[i],r[i]);
90 x:=l[i]; f[l[i]]:=0;
91 end;
92 begin
93 assign(input,'input.txt'); reset(input);
94 assign(output,'ans.txt'); rewrite(output);
95 readln(m);
96 tt:=0; root:=0;
97 for i:=1 to m do
98 begin
99 readln(order,k);
100 case order of
101 1:insert(root,k);
102 2:delete(root,k);
103 3:writeln(find(root,k));
104 end;
105 end;
106 close(input); close(output);
107 end.
108

 

BOB HAN原创 转载请注明出处

 

posted on 2010-08-24 11:43  Master_Chivu  阅读(2411)  评论(0编辑  收藏  举报

导航