Treap学习笔记

前言:没有人比我更容易RE

必定Runtime Excellent的指针来写*衡树。

definition

Treap是*乎*衡的二叉查找树。二叉查找树满足左儿子的权值小于父亲,右儿子的权值大于父亲。

同时由于这样的二叉查找树很容易退化成链,所以我们给树的每个节点随机赋予一个随机权值,并且要求树上关于这个随机权值满足堆的性质。这样随机出来的结果大部分都会*乎*衡,几乎不会退化成链。

build

定义节点

typedef long long int lld;
typedef long long unsigned llu;
class Treap
{
    /// use val-0 for val, ltree less than 0, rtree greater than 0
    /// cnt0 can be 0, means there is no 0
    /// but root should be saved as root
    public:
    lld val,rd;
    llu sz[2],cnt;
    // sz[0] for size without repetition
    // sz[1] for size with repetition
    Treap *lson,*rson;
    Treap(const lld=inf);
    ~Treap(){}
    Treap* maintain(const int=0);
    void cut();
    Treap operator=(const Treap a)
    {
        val=a.val; cnt=a.cnt; sz[0]=a.sz[0]; sz[1]=a.sz[1]; rd=a.rd;
        return *this;
    }
    operator long long int()const
    {
        return val;
    }
    void print(int=0);
};

英语不太好……偶来解释一下啦

定义结构类型Treap,一个Treap变量就表示一个节点。所以每个节点会有以下几个属性:

Treap* lson			指向左儿子地址的指针,没有左儿子时为空
Treap* rson			指向右儿子地址的指针,没有右儿子时为空
lld val				这个节点储存的权值
lld rd				对于Treap节点特别赋予的随机权值。
						可以用std::rand()函数获取,也可以自己写
llu cnt				这个节点的个数,也就是*衡树中数值val的个数
llu sz[0]			这棵子树的大小,重复数字只计算一次
llu sz[1]			这棵子树的大小,重复数字按照实际情况计算
Treap* maintain(const int)	更新这个节点的大小

看前面三行注释……我想说明的是,Treap实现的方法有很多,这里我自创了一种方法。

以权值0为根,所以左子树的权值都小于0,右子树的权值都大于0

初始的时候,令树根为rt, rt->cnt=0; rt->val=0; rt->rd=inf; 然后要求随机权值满足大根堆,所以这个根就一定是根,反过来说权值0的节点一定是根,根的权值一定是0

但是这棵树里面一开始应该什么都没有,所以rt->cnt应该初始化为0

Treap::Treap(const lld r)
{
    /// init the node, as a root
    val=cnt=sz[0]=sz[1]=0;
    lson=rson=0;
    rd=r;
}

建树

void build(Treap **rt)
{
    /// build up a new tree
    srand(time(0));
    if(*rt==0) *rt=new Treap;
    return ;
    (*rt)->cut();
    delete *rt;
    *rt=new Treap;
}
void Treap::cut()
{
    /// cut the tree
    if(lson) lson->cut(), delete lson;
    if(rson) rson->cut(), delete rson;
}

使用这个函数对rt进行初始化。这里rt的数据类型应该是Treap*,但是因为我们要初始化,所以就要传入&rt,也就是Treap**的数据类型,才能修改rt。

如果这是一个空节点,那么直接新建。

否则,先把原来的树砍了,再赋新权值。

当然,我们也可以每次新建树的时候,重新更新随机指针。当然也可以在主函数统一更新。

maintain

维护当前节点的属性

sz[0]是不计算重复下的大小,所以先初始赋值为0/1,sz[1]是计算重复下的大小,所以先初始赋值为cnt

然后我们,如果有儿子,再加上儿子的大小。

特别的,有可能儿子在删除里面删掉了,这个时候儿子的cnt会是0,更新出来儿子的sz[1]也会是0,这个时候我们需要删除儿子释放空间

Treap* Treap::maintain(const int flag)
{
    /// update, if flag, update sons first
    this->sz[0]=(bool)cnt;
    this->sz[1]=cnt;
    if(flag)
    {
        if(lson) lson->maintain(1);
        if(rson) rson->maintain(1);
    }
    if(lson)
    {
        if(lson->sz[1]) sz[0]+=lson->sz[0], sz[1]+=lson->sz[1];
        else delete lson, lson=0;
        /// if lson has no size, it is the empty node which has been deleted
    }
    if(rson)
    {
        if(rson->sz[1]) sz[0]+=rson->sz[0], sz[1]+=rson->sz[1];
        else delete rson, rson=0;
    }
    return this;
}

这里flag是标记,如果flag为1,那么我们就需要先更新儿子,再来维护父亲。我称其为深维护。

如果flag是0,就说明儿子的属性没有改变,不需要更新儿子。就是浅维护。

最后返回当前节点的指针

rotate

我们都知道,对于新建的树,肯定不可能都满足大根堆性质。这个时候我们就要旋转这棵树了。

我们看看这幅图:

从左往右就是左旋的过程,左旋,右旋(其实我也分不清qwq)我这里定义为左旋A,A将成为左儿子

我们可以发现,初始的BST是满足\(D<B<E<A<E<C<F\),这里圆形代表的是一个节点,矩形代表的是一个子树。除了A之外,这些都有可能是空

我们先假设全部都非空,首先左旋,就是A将会成为右儿子C的左儿子,所以要首先断开AC这条边,然后把C挪上去(其实程序不做这一步,只不过画图为了好理解)然后又因为A将成为左儿子,所以就要把C和C的左儿子E之间的边断开,然后再连上A

然后我们就发现E无家可归,我们可以看一下能把他安置到哪里。首先\(E<C\),所以只能放到左子树里面,又因为\(A<C\),所以只能放到A的右子树。这个时候我们恰好发现A的右子树为空,所以E就成为了A的右子树

总结下来,左旋A,只需要记录下A的右儿子C,把A的位置赋给C(比如说是A的父亲的左儿子),所以旋转的时候需要引用传入,这里下面将insert 和remove 的时候会讲。

然后再令A的右儿子为C原来的左儿子,A为C现在的左儿子。

(对啊,代码很短的)

右旋就……完全反过来

我们可以发现,旋转之后还满足BST的要求,而且更换了树的结构。

比如A->rd=2,C->rd=4,原来的BST不满足大根堆性质。但是我们发现A的儿子C的随机权值比A大的时候,我们就会旋,左旋之后C是A的父亲,就满足大根堆性质了

Treap* zig(Treap **id)
{
    /// 引用传入,返回旋转之后的父节点的地址
    /// left-rotate
    if((*id)->val==0) return *id;
    // 根节点不允许旋转,旋转了就……整棵树崩了
    Treap *p=*id,*r;
    if(p->rson==0) return p;
    // 没有右儿子旋什么旋
    r=p->rson;
    p->rson=r->lson; r->lson=p;
    p->maintain();
    r->maintain();
    // 旋转完之后维护子树大小。
    // 旋转只改变了A和C的子树,所以先更新A,然后再更新C(C是A的父亲)
    return *id=r;
    // 更新根节点的位置
}
Treap* zag(Treap **id)
{
    /// right-rotate
    if((*id)->val==0) return *id;
    Treap *p=*id,*l;
    if(p->lson==0) return p;
    l=p->lson;
    p->lson=l->rson; l->rson=p;
    p->maintain();
    l->maintain();
    return *id=l;
}

引用传入建议百度,当然insert里面也会简单涉及一下

insert

插入k,如果当前节点的权值比k大,那就说明k应该在左子树里面,

如果当前节点的权值比k小,那就说明k应该在右子树里面。

如果当前节点的权值等于k,那就说明当前节点的cnt++

特别的,如果我们访问到了空节点,就说明我们需要原地起飞建立一个权值为k的节点

然后,建立完之后,这个节点的rd是随机赋予的,可能会破坏大根堆的性质,我们就需要通过旋转来维护

代码就开始很长了QAQ

Treap* insert(Treap **now,const lld x)
{
    /// insert x to the tree whose root is *now
    if(*now==0)
    {
        *now=new Treap(rand());
        (*now)->val=x;
        (*now)->cnt=1;
        return (*now)->maintain();
    }
    if(x==(*now)->val)
    {
        (*now)->cnt++;
        return (*now)->maintain();
    }
    Treap *p=NULL;
    if(x<(*now)->val)
    {
        p=insert(&(*now)->lson,x);
    }
    if(x>(*now)->val)
    {
        p=insert(&(*now)->rson,x);
    }
    (*now)->maintain();
    const int lrd=(*now)->lson? (*now)->lson->rd:-inf,
    rrd=(*now)->rson? (*now)->rson->rd:-inf;
    if(lrd>rrd&&lrd>(*now)->rd)
    {
        zag(now);
    }else
        if(rrd>lrd&&rrd>(*now)->rd)
        {
            zig(now);
        }
    return p;
}

英语不好的本宝宝又来解释ler……

首先传入的是指针的地址。有些小可爱们可能不理解:指针不就是指向地址吗?为什么有指针的地址?

这是因为,指针变量和其他变量,比如说int,long,一样,都是变量(这不废话么),在c++中,变量是存储在内存里面的,每次我们访问变量,就是通过地址找到内存相对应的空间才能访问变量。

变量用取地址符&就可以得到地址,指针就是指向这个地址一种……数据类型

为什么我们要地址呢?因为众所周知,函数传入参数并不是直接传进去,而是复制一个副本传进去,所以

void fun(int x) { x=10; }
int a=20;
fun(a);

经过了这一系列操作之后,出来的a还是20。这是因为函数只是复制了一份a的值赋给了x进行函数操作。

但是如果我们使用引用/指针传入,就可以改变

引用传入:

void fun(int&x) { x=10; }
int a=20;
fun(a);

这样a出来就是10。因为你的引用符号&(其实就是取地址符号)告诉了编译器我这个参数x是引用传递,你必须把它本身拿过来而不是把值复制一份。

指针传入:

void fun(int *x) { *x=10; }
int a=20;
fun(&a);

这里函数传入的就是一个整形指针参数。这里也是复制了一份,但是没有关系,无论是调用时候传入的&a还是执行时候复制出来的x,他们的值都指向内存里存着a的那一块空间的地址,然后就可以通过地址修改变量。

他们修改的都是一个东西……

所以,指针,是指向地址的变量类型。那么存储这个变量类型,自然也要空间啦。要空间就要内存啦。要内存就有地址啦。

所以我们是可以通过Treap**来传入参数,使得函数内可以改变指针的值。

举个栗子,现在我要通过函数把*rt的左儿子设成空

rt->lson=new Treap;
void fun(Treap* x) { x=NULL; }
fun(rt->lson);

这样只是把rt->lson存储的指向一个Treap节点的地址复制了一份给x,rt->lson本身并没有什么改变

但是如果

rt->lson=new Treap;
void fun(Treap **x) { *x=NULL; }
fun(&rt->lson);

(是的这里引用传入会编译错误我也不知道为什么)

这样就是把rt->lson的地址传了进去,这样函数内*x其实就是rt->lson,这样出阿狸之后rt->lson就是NULL了

为什么这里要用指针传入呢?

我们看一下如果现在rt->lson=NULL,rt->val=0,x=-1

那么x应该插入到rt的左儿子,所以递归下去,但是发现当前左儿子为空,所以insert(lson,x)里面就新建了一个节点,但是无论如何,我怎么知道你新建的是什么节点,我怎么更新rt->lson

当然这个时候你可以说,让函数返回新建节点的地址。这样对于直接父亲是可以的。但是如果我递归了好多层最后才发现是空节点,返回回来……不好做啊

反正我想不到

好吧就和你们用数组写的insert(int&id,int x)要用引用传入的原因是一样的

然后……下面就分别是……等于,大于,小于的操作

然后我们插入完了,但是可能会改变堆的性质喔

我们知道插入完从下面递归回来,我们就要先令其满足堆的性质,所以我们可以知道左右儿子一定都满足堆的性质,只是我比较另类而已,特别的,对于叶子结点,我们认为它满足堆的性质(就一个节点你维护什么维护)

然后我们就可以对比一下左右儿子和我的随机权值,令其中最大的为新的父亲。

特别的,如果没有左或右儿子,令其随机权值为-inf

最后就返回新插入的节点的地址就好啦

真的好长啊

remove

删除k,如果当前节点权值小于k,就说明k只可能出现在右儿子中

如果当前节点权值大于k,就说明k只可能出现在左儿子中。

如果当前节点权值就是k……哦找到你咯

如果删除成功就返回false,删除失败就返回true

对于flag为1的情况,删除所有权值为k的数据,对于flag为0的情况,删除一个权值为k的数据

特别的,如果当前节点为空节点,就说明根本没有插入过k,删除失败,返回true

更容易RE了

bool remove(Treap **id,const lld x,const int flag=0)
{
    /// remove x from *id
    /// if flag remove all the x, else remove one of it
    /// return if it fails
    if(*id==0) return true;
    bool res;
    if((*id)->val==x)
    {
        if(x==0&&(*id)->cnt==0) return true;
        if(flag||(*id)->cnt==1)
        {
            if(x==0||((*id)->lson==0&&(*id)->rson==0))
            {
                /// x==0 is root, root's cnt can be 0
                (*id)->cnt=0;
                (*id)->maintain();
                return false;
            }
            Treap **addme=NULL,*me=NULL;
            if((*id)->rson)
            {
                addme=&(*id)->rson;
                while((*addme)->lson) addme=&(*addme)->lson;
                **id=**addme;
                Treap *j=*addme;
                *addme=(*addme)->rson;
                delete j;
                if((*id)->rson) (*id)->rson->maintain(1);
            }else
            {
                me=(*id)->lson;
                delete *id;
                *id=me;
            }
            if(*id) (*id)->maintain();
            return false;
        }else
        {
            (*id)->cnt--;
            (*id)->maintain();
            return false;
        }
    }else
        if((*id)->val>x)
        {
            res=remove(&(*id)->lson,x,flag);
        }else
            if((*id)->val<x)
            {
                res=remove(&(*id)->rson,x,flag);
            }
    (*id)->maintain();
    const lld lrd=(*id)->lson? (*id)->lson->rd:-inf,
    rrd=(*id)->rson? (*id)->rson->rd:-inf;
    if(lrd>rrd&&lrd>(*id)->rd)
    {
        zag(id);
    }else
        if(rrd>lrd&&rrd>(*id)->rd)
        {
            zig(id);
        }
    return res;
}

我们先讲下边(奸)

如果当前节点不是k,那么就到儿子中处理它

然后删除完之后,是可能改变某些属性的。所以我们先浅维护一下当前节点,

然后在维护一下堆的性质。

最后返回

好了如果k是当前节点呢?

这样只有两种情况:全删和不全删

当且仅当flag=0且cnt大于1时,才不全删。这个时候cnt减一,浅维护当前节点,然后又因为儿子们都没有变,所以直接返回false就可以了

如果是全删,那么要不就是flag为1,要不就是这个节点只有一个。

特别的,如果当前节点是0,而且cnt为0,那么当前树中应该是没有0的,只是一个虚拟根,我们删除的时候要特别处理一下。

回到删除上,如果当前节点是0但是有权值,那我们知道即使全删也不能摧毁当前节点,所以直接cnt化0就好

或者当前节点没有左右儿子,那么删了就删了,不用维护,也是cnt化0

然后浅维护一下当前节点,令其sz[1]为0,等下次维护到他的父节点的时候直接删除

那么其他情况呢?

这里取当前节点的下一个节点(定义为从小到大排序,这个节点的下一个节点)来代替这个节点

为了方便起见,我们不考虑祖先那边有没有下一个节点,我们只考虑本子树内,如果存在这个下一个节点,那么一定是在右子树内。

那么如果没有右子树……我管你有没有左儿子反正就让左儿子来代替俺,就是……直接覆盖掉啦……至于维护大小之类的……回溯到父节点那里会维护的了。

如果有右子树(我才不管你有没有左子树),那么寻找到右子树内的最小值,就是下一个节点,可以证明这个节点没有左儿子(也就是没有比他更小的了),那么就直接让这个节点的右儿子来覆盖掉它。这里注意要用Treap**结构才能保证右儿子取代的是这个节点的地位(比如说父亲的左儿子)

我知道你特别懵(因为我就讲的很迷糊)那么我们明天再讲可以画幅图自己理解消化一下,不急的(我代码就调了三天……qwq所以不推荐大家用指针啦但是指针真的很好用省内存诶嘤)

k-th number

非常著名的寻找第k小(是的到我这里变成了小)问题

如果flag为1,那么计算重复权值。如果flag为0,那么不计算重复权值

同样的,返回找到的节点的指针,失败返回0

什么为之失败呢?比如说访问到空节点啊(就是没有第k号)k为0啊和k大于当前子树大小啊之类的

Treap* kth(Treap *id,const llu k,const int flag=1)
{
    /// find the k-th smallest in the root-id
    /// return the node address, return NULL when fail
    if(id==0) return 0;
    if(k==0) return 0;
    if(k>id->sz[flag]) return 0;
    const llu lsz=id->lson? id->lson->sz[flag]:0;
    if(lsz>=k) return kth(id->lson,k,flag);
    if(k-lsz<=(flag? id->cnt:min(1ull,id->cnt))) return id;
    return kth(id->rson,k-lsz-(flag? id->cnt:min(1ull,id->cnt)),flag);
}

如果左儿子的大小已经比k大,那么说明这棵子树的第k大在左子树,那么直接遍历左子树好了

至于得出是自己那个……理解一下就好我也解释不清

注意就算是flag为0,也有可能id->cnt是0(根节点)这个时候是不能算进去的

然后如果左儿子和自己的总大小加起来都没有k,距离k还差a,那么我们就可以在右儿子里面找第a大,就是整颗子树里面的第k大了

rank

其实和上面那个差不多……先找到这个节点,如果这个节点cnt为0那么返回0(失败)

否则返回1+左儿子大小

lld rank(Treap *id,const lld k,const int flag=1)
{
    /// return the rank of node whose val is k-lsz
    /// return 0 when fail
    if(id==0) return 0;
    const llu lsz=id->lson? id->lson->sz[flag]:0;
    if(id->val==k)
    {
        if(id->cnt==0) return 0;
        return 1+lsz;
    }
    if(k<id->val) return rank(id->lson,k,flag);
    return rank(id->rson,k,flag)+lsz+(flag? id->cnt:min(1ull,id->cnt));
}

pre&nxt

前驱/后记的定义是在这棵树内不大于/不小于给定数的最大数

所以这个给定的k是有可能在树里找不到的

如果找到了那当然最好

因为我们要找的是不大于/不小于,所以直接返回k就可以了

注意也要判断cnt是否为0

如果找不到怎么办呢?

这里用前驱举例子

Treap* pre(Treap *id,const lld k)
{
    /// return the max number which is not greater than k
    if(id==0) return 0;
    if(id->val==k&&id->cnt) return id;
    if(id->val>=k) return pre(id->lson,k);
    Treap* p=pre(id->rson,k);
    if(p) return p;
    if(id->cnt) return id;
    return pre(id->lson,k);
}

如果当前节点比k大,明显不满足不大于k,所以我们要到左儿子里面找

那么就只剩下一种情况,就是当前节点权值小于k

这时候就要分类讨论,有可能前驱在右儿子,所以先找一下。右儿子没有再考虑一下自己。如果自己也不行那么才去左儿子找

后继也一样……吧

Treap* nxt(Treap *id,const lld k)
{
    /// return the min number which is not less than k
    if(id==0) return 0;
    if(id->val==k&&id->cnt) return id;
    if(id->val<=k) return nxt(id->rson,k);
    Treap* p=nxt(id->lson,k);
    if(p) return p;
    if(id->cnt) return id;
    return nxt(id->rson,k);
}

All in all

题目

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <ctime>

namespace ptr_treap
{
	typedef long long int lld;
	typedef long long unsigned llu;
	const lld inf=9223372036854775807;
	template <typename T> inline T min(const T a,const T b) { return a<b? a:b; }
	class Treap
	{
		/// use val-0 for val, ltree less than 0, rtree greater than 0
		/// cnt0 can be 0, means there is no 0
		/// but root should be saved as root
		public:
			lld val,rd;
			llu sz[2],cnt;
			// sz[0] for size without repetition
			// sz[1] for size with repetition
			Treap *lson,*rson;
			Treap(const lld=inf);
			~Treap(){}
			Treap* maintain(const int=0);
			void cut();
			Treap operator=(const Treap a)
			{
				val=a.val; cnt=a.cnt; sz[0]=a.sz[0]; sz[1]=a.sz[1]; rd=a.rd;
				return *this;
			}
			operator long long int()const
			{
				return val;
			}
			void print(int=0);
	};
	void Treap::print(int level)
	{
		printf("%lld\n",val);
		if(lson){
			for(register int i=0;i<=level;++i) putchar(32); printf("|lson:"); lson->print(level+1);
		}
		if(rson){
			for(register int i=0;i<=level;++i) putchar(32); printf("|rson:"); rson->print(level+1); 
		}
	}
	Treap::Treap(const lld r)
	{
		/// init the node, as a root
		val=cnt=sz[0]=sz[1]=0;
		lson=rson=0;
		rd=r;
	}
	Treap* Treap::maintain(const int flag)
	{
		/// update, if flag, update sons first
		this->sz[0]=(bool)cnt;
		this->sz[1]=cnt;
		if(flag)
		{
			if(lson) lson->maintain(1);
			if(rson) rson->maintain(1);
		}
		if(lson)
		{
			if(lson->sz[1]) sz[0]+=lson->sz[0], sz[1]+=lson->sz[1];
			else delete lson, lson=0;
			/// if lson has no size, it is the empty node which has been deleted
		}
		if(rson)
		{
			if(rson->sz[1]) sz[0]+=rson->sz[0], sz[1]+=rson->sz[1];
			else delete rson, rson=0;
		}
		return this;
	}
	void Treap::cut()
	{
		/// cut the tree
		if(lson) lson->cut(), delete lson;
		if(rson) rson->cut(), delete rson;
	}
	
	void build(Treap **rt)
	{
		/// build up a new tree
		srand(time(0));
		if(*rt==0) *rt=new Treap;
		return ;
		(*rt)->cut();
		delete *rt;
		*rt=new Treap;
	}
	
	Treap* zig(Treap **id)
	{
		/// left-rotate
		if((*id)->val==0) return *id;
		Treap *p=*id,*r;
		if(p->rson==0) return p;
		r=p->rson;
		p->rson=r->lson; r->lson=p;
		p->maintain();
		r->maintain();
		return *id=r;
	}
	Treap* zag(Treap **id)
	{
		/// right-rotate
		if((*id)->val==0) return *id;
		Treap *p=*id,*l;
		if(p->lson==0) return p;
		l=p->lson;
		p->lson=l->rson; l->rson=p;
		p->maintain();
		l->maintain();
		return *id=l;
	}
	Treap* insert(Treap **now,const lld x)
	{
		/// insert x to the tree whose root is *now
		if(*now==0)
		{
			*now=new Treap(rand());
			(*now)->val=x;
			(*now)->cnt=1;
			return (*now)->maintain();
		}
		if(x==(*now)->val)
		{
			(*now)->cnt++;
			return (*now)->maintain();
		}
		Treap *p=NULL;
		if(x<(*now)->val)
		{
			p=insert(&(*now)->lson,x);
		}
		if(x>(*now)->val)
		{
			p=insert(&(*now)->rson,x);
		}
		(*now)->maintain();
		const int lrd=(*now)->lson? (*now)->lson->rd:-inf,
		rrd=(*now)->rson? (*now)->rson->rd:-inf;
		if(lrd>rrd&&lrd>(*now)->rd)
		{
			zag(now);
		}else
		if(rrd>lrd&&rrd>(*now)->rd)
		{
			zig(now);
		}
		return p;
	}
	
	bool remove(Treap **id,const lld x,const int flag=0)
	{
		/// remove x from *id
		/// if flag remove all the x, else remove one of it
		/// return if it fails
		if(*id==0) return true;
		bool res;
		if((*id)->val==x)
		{
			if(x==0&&(*id)->cnt==0) return true;
			if(flag||(*id)->cnt==1)
			{
				if(x==0||((*id)->lson==0&&(*id)->rson==0))
				{
					/// x==0 is root, root's cnt can be 0
					(*id)->cnt=0;
					(*id)->maintain();
					return false;
				}
				Treap **addme=NULL,*me=NULL;
				if((*id)->rson)
				{
					addme=&(*id)->rson;
					while((*addme)->lson) addme=&(*addme)->lson;
					**id=**addme;
					Treap *j=*addme;
					*addme=(*addme)->rson;
					delete j;
					if((*id)->rson) (*id)->rson->maintain(1);
				}else
				{
					me=(*id)->lson;
					delete *id;
					*id=me;
				}
				if(*id) (*id)->maintain();
				return false;
			}else
			{
				(*id)->cnt--;
				(*id)->maintain();
				return false;
			}
		}else
		if((*id)->val>x)
		{
			res=remove(&(*id)->lson,x,flag);
		}else
		if((*id)->val<x)
		{
			res=remove(&(*id)->rson,x,flag);
		}
		(*id)->maintain();
		const lld lrd=(*id)->lson? (*id)->lson->rd:-inf,
		rrd=(*id)->rson? (*id)->rson->rd:-inf;
		if(lrd>rrd&&lrd>(*id)->rd)
		{
			zag(id);
		}else
		if(rrd>lrd&&rrd>(*id)->rd)
		{
			zig(id);
		}
		return res;
	}
	
	Treap* kth(Treap *id,const llu k,const int flag=1)
	{
		/// find the k-th smallest in the root-id
		/// return the node address, return NULL when fail
		if(id==0) return 0;
        if(k==0) return 0;
		if(k>id->sz[flag]) return 0;
		const llu lsz=id->lson? id->lson->sz[flag]:0;
		if(lsz>=k) return kth(id->lson,k,flag);
		if(k-lsz<=(flag? id->cnt:min(1ull,id->cnt))) return id;
		return kth(id->rson,k-lsz-(flag? id->cnt:min(1ull,id->cnt)),flag);
	}
	
	lld rank(Treap *id,const lld k,const int flag=1)
	{
		/// return the rank of node whose val is k-lsz
		/// return 0 when fail
		if(id==0) return 0;
		const llu lsz=id->lson? id->lson->sz[flag]:0;
		if(id->val==k)
		{
			if(id->cnt==0) return 0;
			return 1+lsz;
		}
		if(k<id->val) return rank(id->lson,k,flag);
		return rank(id->rson,k,flag)+lsz+(flag? id->cnt:min(1ull,id->cnt));
	}
	
	Treap* pre(Treap *id,const lld k)
	{
		/// return the max number which is not greater than k
		if(id==0) return 0;
		if(id->val==k&&id->cnt) return id;
		if(id->val>=k) return pre(id->lson,k);
		Treap* p=pre(id->rson,k);
		if(p) return p;
		if(id->cnt) return id;
		return pre(id->lson,k);
	}
	
	Treap* nxt(Treap *id,const lld k)
	{
		/// return the min number which is not less than k
		if(id==0) return 0;
		if(id->val==k&&id->cnt) return id;
		if(id->val<=k) return nxt(id->rson,k);
		Treap* p=nxt(id->lson,k);
		if(p) return p;
		if(id->cnt) return id;
		return nxt(id->rson,k);
	}
}

ptr_treap::Treap *rt;
int opt, x, n;
int main()
{
	// freopen("P3369_6.in","r",stdin);
	build(&rt);
	scanf("%d",&n);
	while(n--)
	{
		scanf("%d%d",&opt,&x);
		switch(opt)
		{
			case 1:
				ptr_treap::insert(&rt,x);
				break;
			case 2:
				ptr_treap::remove(&rt,x);
				break;
			case 3:
				printf("%lld\n",ptr_treap::rank(rt,x));
				break;
			case 4:
				printf("%lld\n",(ptr_treap::lld)*ptr_treap::kth(rt,x));
				break;
			case 5:
				printf("%lld\n",(ptr_treap::lld)*ptr_treap::pre(rt,x-1));
				break;
			case 6:
				printf("%lld\n",(ptr_treap::lld)*ptr_treap::nxt(rt,x+1));
				break;
			default:
				break;
		}
		// rt->print();
	}
	return 0;
}
posted @ 2021-07-23 19:43  IdanSuce  阅读(64)  评论(0编辑  收藏  举报