Splay 复习
0. 前言
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 x 数
- 删除 x 数(若有多个相同的数,因只删除一个)
- 查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
- 查询排名为 x 的数
- 求 x 的前驱(前驱定义为小于 x,且最大的数)
- 求 x 的后继(后继定义为大于 x,且最小的数)
1. 二叉查找树(BST)
维护一堆数,要求插入删除,不支持修改,要求查询排名。。。。。。
这些正是BST要干的事情!
二叉查找树,首先是一棵二叉树。那么一个结点就应该有两个儿子(空儿子也算一个儿子)。
然后每个结点都储存着一些信息(权值)。我们可以人为定义函数template<typename T> bool comp(T,T);
,如果comp(a,b)==true
就表示a
比b
小。
那么二叉查找树的一个特点就是:
- 所有结点权值都不同(
a,b
相同定义为comp(a,b)==false and comp(b,a)==false
)
也就意味着结点需要一个变量来记录相同权值的个数。如果要写的数据结构为不重集,则不需要。 - 权值小于当前节点的结点都在左子树内(如果左儿子为空结点则不存在这样的数)
- 权值大于当前节点的结点都在右子树内(
a
比b
大定义为comp(b,a)==true
)
BST的一些基本操作:
- 插入 x
首先尝试寻找这棵树中是否存在权值与x相同的结点,如果存在就相同权值大小加一
如果不存在,则在父节点时新建权值为x的结点
比如,当前结点权值为\(100\),而x是\(1\),根据特点2可以得知x的结点应该在当前结点的左子树内。于是我们就要往左子树寻找x的结点
但是如果当前结点左儿子为空结点怎么办?那么我们可以直接新建左儿子,权值就是x,就完成了插入 - 删除 x
对于不同的实现方法有所差异,后文会对splay实现的BST的删除方法有所提及 - 查询 x 的排名
对于不同的实现方法有所差异,后文会有所提及。 - 查询排名为k的数
如果左子树的大小已经大于等于k,那么整棵树内排名为k的数也就是左子树内排名为k的数,递归寻找即可
如果左子树的大小小于k,但是左子树的大小加上父结点相同权值的个数,记他们的和为\(tot\),如果\(tot\)大于等于k,那么这个排名为k的数就是父节点的权值
如果\(tot\)仍然小于k,那整棵树内排名为k的数就是右子树内排名为\(k-tot\)的数或者根本不存在 - 求 x 的前驱/后继
对于不同的实现方法有所差异,后文会有所提及。
可以看出来,这样一棵BST最好就是一棵满二叉树,这样一次操作的时间复杂度就是\(O(\log_2n)\)
但是如果一棵BST已经退化成为一条链,那么每次操作的时间复杂度就是\(O(n)\)
所以我们就要维护BST,在不改变三个特点的前提下使得他尽量不会退化成一条链。
其实,三个特点加起来就是一句话:BST的中序遍历为严格单调上升序列(即不存在相同元素。同时在元素个数和大小确定的情况下,每个元素在中序遍历中的位置不变,即中序遍历不变。)
那么,下面就介绍一种方法
2. Splay
Splay实现的BST本质上是为了迎合数据的趋势。比如说我的BST里面权值为\(100\)的结点在第10层,那么我如果若干次都询问100的排名,那么每次我们就要跑10层才能找到结果。但是如果权值为\(100\)的结点在第一层(也就是根结点),那么我们每次便能光速获取100的信息。
所以Splay的核心思想就是每次操作完一个数(无论是插入还是删除(如果没删完)还是查询排名)只要操作了,就把他弄到根结点。
那么,我们如何在保证中序遍历的情况下实现这些操作呢?就需要介绍一个很奇妙的方法:旋转。
首先我们知道,对于一条链来说,无论以谁为根结点,他的中序遍历都是不会变的。
而在这时,如果我们选择链的中点作为根结点,这棵树的深度就有最小值。而深度最小值正是我们最想要的(因为这样平均复杂度就会最小)
当然,我们选择不同的根,树的形态也会随之改变,但是无论如何,中序遍历都不会变。
假设以2号结点为根时,右儿子时3号结点,那么将3号结点向上旋转一层,他就取代2号结点变成了根结点
所以Splay的核心操作1:旋转某个结点,使其取代父节点的位置
通俗来说,如果结点x的父亲为y,那么这棵子树的根就是y,而旋转x就是要在保证中序遍历不变的情况下使得x变成这棵子树的根结点。
为什么要这样?我们可以知道,旋转只是在子树内进行。而无论怎么旋转,这棵子树的根结点在整棵树中的深度都是不变的,设为\(dep\)。那么只要x当了根结点,他的深度就从原来的\(dep+1\)(因为是y的儿子)变成了\(dep\),这样就更靠近根了。而Splay的核心思想就是把一个结点旋转为整颗树的根结点
下面给出树的结构代码(题目传送门)
#include <cstdio>
#include <functional>
#ifndef ONLINE_JUDGE // 如果不是在线评测,在本地推荐使用文件输入输出
FILE *const infile=fopen("splay.in","r"), *const outfile=fopen("splay.out","w"); // 打开文件
#define scanf(x...) fscanf(infile,x) // 宏定义全部为文件输入
#define printf(x...) fprintf(outfile,x) // 宏定义全部为文件输出
#endif // ONLINE_JUDGE
#define ls sn[0] // 为了方便旋转,左右儿子需要存在一个数组中
#define rs sn[1] // 为了方便表述,使用ls和rs。因此可以使用宏定义
template <typename T, class Cmp=std::less<T> > // 可以自定义类型和比较大小的方法
class Splay // 仿STL对底层进行包装
{
protected:
Cmp comp; // 定义比较大小的方法
class splay // 真正底层操作的树结构结点
{
friend class Splay; // 声明友元,使得外层可以直接调用内部成员
splay *sn[2], *fa; // 定义两个儿子和父亲的指针
size_t cnt,sze; // 分别是当前结点权值个数和当前子树的大小
T dat; // 权值
splay(splay* f=0x0, splay* s=0x0) { cnt=sze=0; fa=f; sn[0]=sn[1]=s; } // 初始化一个新结点
const int chk() const { return this == fa->rs; } // 获取当前结点的连接情况。如果当前结点是父节点的左儿子则返回0,右儿子则返回1
}*rt; // 根结点
splay* const None; // 空结点。因为空结点可以用一个结点统一表示,所以只需要定义一个
// 定义了空结点就可以在一些操作中不进行空结点的判断。
inline size_t maintain(splay* x) { return x->sze=x->ls->sze+x->rs->sze+x->cnt; } // 更新x子树的大小
inline size_t rotate(splay*); // 将给定结点往上旋转一层
inline splay* up(splay*,splay* =0x0); // 将给定结点上旋直到结点的父亲是给定结点
splay* ins(T); // 内置-插入函数
splay* erase(); // 内置-删除函数
splay* fd(T); // 内置-查找函数
size_t rk(T); // 内置-排名函数
splay* th(size_t); // 内置-求排名为给定非负整数的结点的权值的函数
splay* pre(T); // 内置-前驱函数
splay* suf(T); // 内置-后继函数
splay* max(splay* =0x0); // 内置-查找给定子树内最大值的函数
splay* min(splay* =0x0); // 内置-查找给定子树内最小值的函数
public:
Splay():None(rt=new splay) { None->ls=None->rs=None->fa=None; } // 整个大结构体初始化
class iterator // 定义迭代器方便外部调用
{
private:
splay* M_current; // 迭代器指向的结点
public:
iterator(splay* x=0x0):M_current(x) {} // 初始化迭代器
inline splay* what() const { return M_current; } // 返回指向的结点地址
inline T operator*() const { return M_current->dat; } // 返回指向结点的权值
inline T* operator->() const { return &(M_current->dat); } // 返回指向结点权值的指针
bool operator == (iterator b) const { return M_current == b.M_current; } // 判断两个迭代器指向的结点是否相同
iterator operator ++ (); // 自增函数(指向下一个)
iterator operator -- (); // 自减函数(指向上一个)
};
inline iterator begin() const { return iterator(rt); } // 返回始迭代器
inline iterator end() const { return iterator(None); } // 返回终迭代器
inline iterator insert(T dat) // 包装-插入函数
{
if(rt==None) // 如果当前根结点为空结点,即还没有任何数据
{
rt=new splay(None,None); // 则为根结点申请一个结点
rt->dat=dat; // 权值就是当前权值
return iterator(rt); // 然后返回根结点的迭代器
}
return iterator(up(ins(dat))); // 否则就先调用内置函数插入,并把插入的结点旋转为根结点,然后返回根结点的迭代器
}
inline iterator find(T dat) { return iterator(up(fd(dat))); } // 调用内置函数进行查找,找到后将其旋转为根,然后返回根结点的迭代器
inline int remove(T dat) // 包装-删除函数
{
splay*p=fd(dat); if(p==None) return 1; // 先找到该权值的结点。如果没找到,则删除失败。
// 然后将其旋转到根结点。
if(--up(p)->cnt) return 0; // 如果他的个数不止一个,那么就减少一个好了。
return erase(); // 否则,调用内置函数删除根结点并合并左右子树
}
inline size_t rank(T dat) // 包装=排名函数
{
splay*p=fd(dat); if(p==None) return 0; // 先找,没找到,则查找失败,返回0
return up(p)->ls->sze+1; // 将其旋转到根结点,那么根据排名的定义,左子树的大小就是比他小的元素个数
}
inline iterator kth(size_t x) // 包装-查找排名为x的数
{
if(x>rt->sze) return end(); // x大于元素个数,没法找了直接返回终迭代器
return iterator(up(th(x))); // 调用内置函数找到该结点,旋转至根并返回其迭代器
}
inline iterator prefix(T x) { return iterator(up(pre(x))); } // 调用内置函数返回前驱结点,将其旋转至根并返回迭代器
inline iterator suffix(T x) { return iterator(up(suf(x))); } // 调用内置函数返回后继结点,将其旋转至根并返回迭代器
};
#undef ls // 及时取消宏定义是个好习惯
#undef rs
Splay<int> BST;
int n;
int opt,x;
int main()
{
scanf("%d",&x);
while(n--)
{
scanf("%d%d",&opt,&x);
switch(opt)
{
case 1:
BST.insert(x);
break;
case 2:
BST.remove(x);
break;
case 3:
printf("%d\n",(int)BST.rank(x));
break;
case 4:
printf("%d\n",*BST.kth(x)); // 注意返回的是迭代器,需要规范化操作。
break;
case 5:
printf("%d\n",*BST.prefix(x));
break;
case 6:
printf("%d\n",*BST.suffix(x));
break;
default:
break;
}
}
#ifndef ONLINE_JUDGE
fclose(infile); fclose(outfile); // 及时关闭文件是个好习惯
#undef scanf
#undef printf
#endif // ONLINE_JUDGE
return 0;
}
接下来一个个填补没有实现的函数
为了节省空间,这里将把函数的声明和实现放在一起。
内置-旋转函数
inline size_t rotate(splay* x) // 将给定结点x上旋一层并返回旋转后的子树大小
{
if(x==rt) return maintain(x); // 已经是根结点,没法旋了,直接返回
if(x==None) return 0; // 空节点不能旋转
splay*y=x->fa; const int chk=x->chk(); // 获取父亲结点的信息
y->sn[chk]=x->sn[chk^1]; x->sn[chk^1]->fa=y; // 处理儿子关系
y->fa->sn[y->chk()]=x; x->fa=y->fa; // 处理父亲关系
x->sn[chk^1]=y; y->fa=x; // 处理内部关系
None->ls=None->rs=None->fa=None; // 维护空节点
maintain(y); return maintain(x); // 更新大小并返回。注意要先更新y
}
解释一下处理关系的那三行。
首先该代码兼容左儿子和右儿子。无论旋转前x是y的左儿子还是右儿子该代码都可以正确执行。
那么就假定x是y的左儿子,右儿子同理。则chk=0
那么我们脑海中就应该有一副图景:旋转前x是y的左儿子,旋转后y就应该是x的右儿子(根据二叉查找树的定义)
那么原来x的左儿子就还是x的左儿子,不需要变。那么原来x的右子树哪去了?
我们发现y原来的左儿子是x,现在x成了父亲,y的左子树就空缺了。然后x的右儿子虽然比x大,但是还在x子树内,就应该比y小,所以原来x的右子树刚好可以落在y的左子树上。
于是就有了处理儿子关系这一行,把x的右儿子变成y的左儿子。
y->sn[chk]=x->sn[chk^1]; x->sn[chk^1]->fa=y; // 注意同时也要把(原来x的右儿子)的父亲改成y
然后我们是不是只要连一下x,y之间的这条边就可以了?不急,我们还需要处理父亲关系。总不能x明明已经取代了y的位置,结果从根结点找下来第一个找到的还是y吧?多委屈啊。
y->fa->sn[y->chk()]=x; x->fa=y->fa; // 因为y->chk()只用到一次,就没必要用变量存了。
最后,我们再连x,y之间的边。
x->sn[chk^1]=y; y->fa=x;
就顺利完成了一次旋转
然后有没有发现,我们因为使用了空结点,所以即使父亲是空节点,而我们强行连边也不会导致运行错误。但是唯一的问题就是如果修改空结点的儿子,那么就会造成答案错误。所以在旋转完之后,我们还要维护一下,还原空结点。
内置-上旋函数
我把他命名为上旋函数,主要就是他一次性执行多次旋转,每次都能抬高给定结点的深度。
有一个很简单粗暴的写法
inline splay* up(splay* x,splay*top =0x0) // 将给定结点抬升至父结点为top
{
if(x==None) return None; // 空结点不能进行操作
if(!top) top=None; // 没有给定结点,就抬升到父结点为空节点,即x会变成根结点
while(x->fa!=top) rotate(x); // 只要没到,一路旋转
if(top==None) rt=x; // 如果没给定结点,那么就是一路旋到根,那么根指针指向的结点也要变。
return x; // 返回
}
但是如果我们吃饱了撑着没事干我们就会发现,当x是父亲的左儿子而且父亲是爷爷的左儿子,或者x是父亲的右儿子而且父亲是爷爷的右儿子时,我们这样连续旋转,是不如先把父亲旋转一次,再把x旋转一次更好的。
这是因为(自己画个图看看)前者只是单纯把x旋转上去,而后者在旋转上去的同时将树的深度减少了一层!
所以我们稍加改进
inline splay* up(splay* x,splay*top =0x0)
{
if(x==None) return None;
if(!top) top=None;
while(x->fa!=top)
{
if(x->fa->fa!=top) rotate(x->fa->chk() == x->chk()? x->fa:x); // 如果是同一类型的儿子,那么先旋转父亲。否则直接旋转x
rotate(x); // 再旋一次
}
if(top==None) rt=x;
return x;
}
内置-插入函数
插入的思路在上面讲过了,这里略微提一下
splay* ins(T dat) // 将数据插入树中
{
splay *p=rt; // 先定义一个指针指向根结点
while(p->cnt) // 如果当前结点有权值个数(下面会解释)
{
if(comp(dat,p->dat)) // 如果插入的权值小于当前结点的权值,那么应该往左儿子插入
{
if(p->ls==None) p->ls=new splay(p,None); // 如果左儿子为空,则为左儿子申请一个结点
p=p->ls; // 往左儿子迭代
}else
if(comp(p->dat,dat)) // 如果插入的权值大于当前结点权值,那么应该往右儿子插入
{
if(p->rs==None) p->rs=new splay(p,None); // 只有新申请的结点权值个数才会是0,所以上面如果权值个数是0就代表是新结点,可以退出
p=p->rs;
}else break; // 否则,就是权值相等,可以退出
}
++p->cnt; ++p->sze; // 权值个数加一
p->dat=dat;
return p; // 返回当前结点
}
内置-最大(小)值
由于实现原理类似,所以放在一起讲了
inline splay* max(splay*x =0x0) // 码量挺少的所以感觉可以写内联
{
if(!x) x=rt; // 没有给定结点,即寻找整颗子树
if(x==None) return None; // 如果给定结点是空结点或者根结点就是空结点,那么就没有最大值
while(x->rs!=None) x=x->rs; // 否则,当前结点一路访问右儿子直到没有右儿子就是最大值
return x;
}
inline splay* min(splay*x =0x0)
{
if(!x) x=rt;
if(x==None) return None;
while(x->ls!=None) x=x->ls; // 直到没有左儿子就是最小值
return x;
}
内置-删除函数
这个删除函数其实功能比较弱,他只能删除根结点。所以我们通过包装把需要删除的结点旋到根,然后删除这个结点,并合并左右子树。
合并左右子树的方法:把左子树最大值旋转到左子树的根,那么此时左子树的根就是左子树的最大值,他就没有右儿子。那么就可以把原来的右子树作为现在的右儿子,并把现在左子树的根作为整棵树的根。
注意特判左子树为空的情况
int erase() // 外部包装的时候已经判断过不需要删除结点的情况,因此现在是一定要删除根结点了
{
if(rt->ls==None and rt->rs==None) rt=None; // 孤零零一个结点,删了之后整棵树就是空
else if((rt->ls==None) ^ (rt->rs==None)) // 否则,如果只有一个子树为空,
{
rt=rt->sn[rt->ls==None]; // 那么就直接指定不为空的那棵子树为新树,根结点就是子树根结点
delete rt->fa; // 删除原来根结点
rt->fa=None;
}else // 否则,就是两棵子树都不为空
{
up(max(rt->ls),rt); // 左子树不为空,所以可以旋转左子树最大值上来
rt->ls->rs=rt->rs; rt->rs->fa=rt->rs; // 连接左右子树
rt=rt->ls; // 指定新的根
delete rt->fa; // 删除旧的根
rt->fa=None;
}
maintain(rt); // 更新大小
return 0;
}
内置-查找函数
inline splay* fd(T dat) // 感觉码量挺少的所以加个内联
{
splay* p=rt; // 从根结点开始
while(p->cnt) // 如果遇到空结点就退出
{
if(comp(dat,p->dat)) p=p->ls; // dat < p->dat,即我们要找的数据在左子树
else if(comp(p->dat,dat)) p=p->rs; // 在右子树
else break; // 就是当前结点,直接退出
}
return p; // 返回找到的结点
}
内置-排名函数
完全没用,包装里面写的很清楚了
inline size_t rank(T dat)
{
splay*p=fd(dat); if(p==None) return 0;
return up(p)->ls->sze+1;
}
并没有用到内置的排名函数,只需要使用内置的查找函数找到结点并返回左子树大小+1就好
内置-第几名函数
splay* th(size_t k)
{
splay* p=rt; // 从根结点开始寻找第k个
while(k>0) // k必须是正整数
{
if(p==None) return None; // 非法访问到空结点,退出
if(k<=p->ls->sze) p=p->ls; // 点击上方说明查看
else if(k<=p->ls->sze+p->cnt) return p;
else if((k-=p->ls->sze+p->cnt)<=p->rs->sze) p=p->rs;
else return None;
}
return None;
}
有些时候如果我们觉得k<=p->ls->sze+p->cnt; k-=p->ls->sze+p->cnt
太过繁琐,能不能第一次就(k-=p->ls->sze+p->cnt)<=0
,然后第二次直接判断k<=p->rs->sze
呢?
本来我也是这样的思路,然后打了一下,发现不行。因为size_t k
,是一个无符号类型,除非你刚好卡到点子0上,否则无论如何他都不会满足<=0
这个条件,所以对于无符号类型应该先判断再减。
内置-前驱/后继函数
由于实现方法相似所以放在一起讲了
splay* pre(T dat)
{
splay* res=max(up(ins(dat))->ls); // 这步应该拆分成以下几步:
// 1. splay*p = ins(dat); - 由于原来可能没有这个结点,所以要先插入
// 2. p = up(p); - 把这个新结点旋转到根
// 3. splay* res = max(p->ls); - 根结点权值就是给定的权值,找他的前驱就是找左子树内最大值
if(!--rt->cnt) erase(); // 找到之后别忘了删除插入的结点(就是根结点)
return res;
}
splay* suf(T)
{
splay* res=min(up(ins(dat))->rs); // 后继就是在右子树中查找最小值
if(!--rt->cnt) erase();
return res;
}
讲完啦!
Code
#include <cstdio>
#include <functional>
#ifndef ONLINE_JUDGE
FILE *const infile=fopen("splay.in","r"), *const outfile=fopen("splay.out","w");
#define scanf(x...) fscanf(infile,x)
#define printf(x...) fprintf(outfile,x)
#endif // ONLINE_JUDGE
#define ls sn[0]
#define rs sn[1]
template <typename T, class Cmp=std::less<T> >
class Splay
{
protected:
Cmp comp;
class splay
{
friend class Splay;
splay *sn[2], *fa;
size_t cnt,sze;
T dat;
splay(splay* f=0x0, splay* s=0x0) { cnt=sze=0; fa=f; sn[0]=sn[1]=s; }
const int chk() const { return this == fa->rs; }
}*rt;
splay* const None;
inline size_t maintain(splay* x) { return x->sze=x->ls->sze+x->rs->sze+x->cnt; }
inline size_t rotate(splay* x)
{
if(x==rt) return maintain(x);
if(x==None) return 0;
splay*y=x->fa; const int chk=x->chk();
y->sn[chk]=x->sn[chk^1]; x->sn[chk^1]->fa=y;
y->fa->sn[y->chk()]=x; x->fa=y->fa;
x->sn[chk^1]=y; y->fa=x;
None->ls=None->rs=None->fa=None;
maintain(y); return maintain(x);
}
inline splay* up(splay* x,splay*top =0x0)
{
if(x==None) return None;
if(!top) top=None;
while(x->fa!=top)
{
if(x->fa->fa!=top) rotate(x->fa->chk() == x->chk()? x->fa:x);
rotate(x);
}
if(top==None) rt=x;
return x;
}
splay* ins(T dat)
{
splay *p=rt;
while(p->cnt)
{
if(comp(dat,p->dat))
{
if(p->ls==None) p->ls=new splay(p,None);
p=p->ls;
}else
if(comp(p->dat,dat))
{
if(p->rs==None) p->rs=new splay(p,None);
p=p->rs;
}else break;
}
++p->cnt; ++p->sze;
p->dat=dat;
return p;
}
inline splay* max(splay*x =0x0)
{
if(!x) x=rt;
if(x==None) return None;
while(x->rs!=None) x=x->rs;
return x;
}
inline splay* min(splay*x =0x0)
{
if(!x) x=rt;
if(x==None) return None;
while(x->ls!=None) x=x->ls;
return x;
}
int erase()
{
if(rt->ls==None and rt->rs==None) rt=None;
else if((rt->ls==None) ^ (rt->rs==None))
{
rt=rt->sn[rt->ls==None];
delete rt->fa;
rt->fa=None;
}else
{
up(max(rt->ls),rt);
rt->ls->rs=rt->rs; rt=rt->rs->fa=rt->ls;
delete rt->fa;
rt->fa=None;
}
maintain(rt);
return 0;
}
inline splay* fd(T dat)
{
splay* p=rt;
while(p->cnt)
{
if(comp(dat,p->dat)) p=p->ls;
else if(comp(p->dat,dat)) p=p->rs;
else break;
}
return p;
}
splay* th(size_t k)
{
splay* p=rt;
while(k>0)
{
if(p==None) return None;
if(k<=p->ls->sze) p=p->ls;
else if(k<=p->ls->sze+p->cnt) return p;
else if((k-=p->ls->sze+p->cnt)<=p->rs->sze) p=p->rs;
else return None;
}
return None;
}
splay* pre(T dat)
{
splay* res=max(up(ins(dat))->ls);
if(!--rt->cnt) erase();
return res;
}
splay* suf(T dat)
{
splay* res=min(up(ins(dat))->rs);
if(!--rt->cnt) erase();
return res;
}
public:
Splay():None(rt=new splay) { None->ls=None->rs=None->fa=None; }
class iterator
{
private:
splay* M_current;
public:
iterator(splay* x=0x0):M_current(x) {}
inline splay* what() const { return M_current; }
inline T operator*() const { return M_current->dat; }
inline T* operator->() const { return &(M_current->dat); }
bool operator == (iterator b) const { return M_current == b.M_current; }
iterator operator ++ ();
iterator operator -- ();
};
inline iterator begin() const { return iterator(rt); }
inline iterator end() const { return iterator(None); }
inline iterator insert(T dat)
{
if(rt==None)
{
rt=new splay(None,None);
rt->dat=dat;
rt->cnt=rt->sze=1;
return iterator(rt);
}
return iterator(up(ins(dat)));
}
inline iterator find(T dat) { return iterator(up(fd(dat))); }
inline int remove(T dat)
{
splay*p=fd(dat); if(p==None) return 1;
p=up(p);
if(--up(p)->cnt) return 0;
return erase();
}
inline size_t rank(T dat)
{
splay*p=fd(dat); if(p==None) return 0;
return up(p)->ls->sze+1;
}
inline iterator kth(size_t x)
{
if(x>rt->sze) return end();
if(rt==None) return end();
return iterator(up(th(x)));
}
inline iterator prefix(T x) { return iterator(up(pre(x))); }
inline iterator suffix(T x) { return iterator(up(suf(x))); }
};
#undef ls
#undef rs
Splay<int> BST;
int n;
int opt,x;
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&opt,&x);
switch(opt)
{
case 1:
BST.insert(x);
break;
case 2:
BST.remove(x);
break;
case 3:
printf("%d\n",(int)BST.rank(x));
break;
case 4:
printf("%d\n",*BST.kth(x));
break;
case 5:
printf("%d\n",*(BST.prefix(x)));
break;
case 6:
printf("%d\n",*(BST.suffix(x)));
break;
default:
break;
}
}
#ifndef ONLINE_JUDGE
fclose(infile); fclose(outfile);
#undef scanf
#undef printf
#endif // ONLINE_JUDGE
return 0;
}