Splay 复习

0. 前言

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x 数
  2. 删除 x 数(若有多个相同的数,因只删除一个)
  3. 查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
  4. 查询排名为 x 的数
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)
  6. 求 x 的后继(后继定义为大于 x,且最小的数)

1. 二叉查找树(BST)

维护一堆数,要求插入删除,不支持修改,要求查询排名。。。。。。

这些正是BST要干的事情!

二叉查找树,首先是一棵二叉树。那么一个结点就应该有两个儿子(空儿子也算一个儿子)。

然后每个结点都储存着一些信息(权值)。我们可以人为定义函数template<typename T> bool comp(T,T);,如果comp(a,b)==true就表示ab小。

那么二叉查找树的一个特点就是:

  1. 所有结点权值都不同(a,b相同定义为comp(a,b)==false and comp(b,a)==false
    也就意味着结点需要一个变量来记录相同权值的个数。如果要写的数据结构为不重集,则不需要。
  2. 权值小于当前节点的结点都在左子树内(如果左儿子为空结点则不存在这样的数)
  3. 权值大于当前节点的结点都在右子树内(ab大定义为comp(b,a)==true

BST的一些基本操作:

  1. 插入 x
    首先尝试寻找这棵树中是否存在权值与x相同的结点,如果存在就相同权值大小加一
    如果不存在,则在父节点时新建权值为x的结点
    比如,当前结点权值为\(100\),而x是\(1\),根据特点2可以得知x的结点应该在当前结点的左子树内。于是我们就要往左子树寻找x的结点
    但是如果当前结点左儿子为空结点怎么办?那么我们可以直接新建左儿子,权值就是x,就完成了插入
  2. 删除 x
    对于不同的实现方法有所差异,后文会对splay实现的BST的删除方法有所提及
  3. 查询 x 的排名
    对于不同的实现方法有所差异,后文会有所提及。
  4. 查询排名为k的数
    如果左子树的大小已经大于等于k,那么整棵树内排名为k的数也就是左子树内排名为k的数,递归寻找即可
    如果左子树的大小小于k,但是左子树的大小加上父结点相同权值的个数,记他们的和为\(tot\),如果\(tot\)大于等于k,那么这个排名为k的数就是父节点的权值
    如果\(tot\)仍然小于k,那整棵树内排名为k的数就是右子树内排名为\(k-tot\)的数或者根本不存在
  5. 求 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;
}
posted @ 2022-05-23 13:46  IdanSuce  阅读(30)  评论(0编辑  收藏  举报