[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原创 转载请注明出处

 

你可能感兴趣的:(play)