学习笔记-平衡树

学习笔记-平衡树

treap

#include<bits/stdc++.h>
#define int long long
using namespace std;
#define ls t[x].ch[0]
#define rs t[x].ch[1]
const int N=114514;
const int inf=2147483647;
int cnt=0,root;
mt19937 rnd(0x7f);
struct treap{
	int ch[2], cnt, size, val, rd;
	//treap不需要记录父指针,rd表示节点的随机值
}t[N];

void up(int x){
	t[x].size = t[t[x].ch[0]].size+t[t[x].ch[1]].size+t[x].cnt;
}

void rotate(int &x, int d){//x代表的是旋转时作为父节点的节点,d代表的是旋转的方向
//d==0时是左儿子旋上来, d==1是右儿子旋上来.
    int son = t[x].ch[d];
    t[x].ch[d] = t[son].ch[d^1];
    t[son].ch[d^1] = x; up(x), up(x=son);//相当于up(son)
}

void insert(int &x, int val){
    if(!x){//找到对应位置就新建节点
        x = ++cnt;
        t[x].cnt = t[x].size = 1;
        t[x].val = val, t[x].rd = rnd();
        return;
    }
    t[x].size++;//因为插入了数,所以在路径上每个节点的size都会加1
    if(t[x].val == val){t[x].cnt++; return;}//找到了直接返回
    int d = t[x].val < val; insert(t[x].ch[d], val);//否则递归查找插入位置
    if(t[x].rd > t[t[x].ch[d]].rd) rotate(x, d);
}

void delet(int &x, int val){
    if(!x) return;//防止越界
    if(t[x].val == val){
        if(t[x].cnt > 1){t[x].cnt--, t[x].size--;return;}//有相同的就直接cnt--
        bool d = t[ls].rd > t[rs].rd;
        if(ls == 0 || rs == 0) x = ls+rs;//只有一个儿子就直接把那个儿子放到这个位置
        else rotate(x, d), delet(x, val);//否则将x旋下去,找一个随机值小的替代,直到回到1,2种情况
    }
    else t[x].size--, delet(t[x].ch[t[x].val<val], val);//递归找到要删除的节点.
}

int rk(int x, int val){
    if(!x) return 0;
    if(t[x].val == val) return t[ls].size+1;//找到了就返回最小的那个
    if(t[x].val > val) return rk(ls, val);//如果查找的数在x的左边,则直接往左边查
    return rk(rs, val)+t[ls].size+t[x].cnt;//否则往右边查,左边的所有数累加进答案
}

int kth(int root, int k){
    int x = root;
    while(1){
        if(k <= t[ls].size) x = ls;
        else if(k > t[ls].size+t[x].cnt)
            k -= t[ls].size+t[x].cnt, x = rs;
		else return t[x].val;
    }
}

int pre(int x, int val){
    if(!x) return -inf;//防止越界,同时-inf无法更新答案,
    if(t[x].val >= val) return pre(ls, val);//如果该节点的权值大于等于要找的权值
    //则不能成为前驱,递归查找左子树(有可能找到前驱)
    return max(pre(rs, val), t[x].val);//找右子树中是否存在前驱
}

int nex(int x, int val){//同上
    if(!x) return inf;
    if(t[x].val <= val) return nex(rs, val);
    return min(nex(ls, val), t[x].val);
}

signed main(){
	#ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
	int m,op,x;
    scanf("%lld",&m);
    while(m--)
    {
        scanf("%lld%lld",&op,&x);
        switch(op)
        {
            case 1:{insert(root,x);break;}
            case 2:{delet(root,x);break;}
            case 3:{printf("%lld\n",rk(root,x));break;}
            case 4:{printf("%lld\n",kth(root,x));break;}
            case 5:{printf("%lld\n",pre(root,x));break;}
            case 6:{printf("%lld\n",nex(root,x));break;}
        }
    }
}

FHQ-treap

FHQ,即无旋treap
众所周知,treap是通过保持堆性质让二叉搜索树唯一,但是旋转太麻烦了,而且不好可持久化,非常的难受,那么FHQ应运而生
首先,FHQ的基本操作有两个,split和merge
split就是将一棵树分成两棵,其中一棵中最大权值小于另一棵最小权值
剩下的都在代码注释

#include<bits/stdc++.h>
#define N 210000
#define llt long long
using namespace std;
llt root,m,op,x,tot; 
struct TREAP
{
    #define ls(now) tree[now].son[0]
    #define rs(now) tree[now].son[1]
    #define v(now)  tree[now].node
    #define upd(now)    tree[now].siz=tree[now].cnt+tree[ls(now)].siz+tree[rs(now)].siz
    struct NODE{llt node,son[2],rd,cnt,siz;}tree[N];llt sz;
    llt create(llt value){v(++sz)=value;tree[sz].cnt=tree[sz].siz=1;tree[sz].rd=rand();return sz; }
    void split(llt now,llt num,llt &x,llt &y)//按值分裂
    {
        if(!now) {x=y=0;return;}
        if(v(now)>num)  y=now,split(ls(now),num,x,ls(now));//这个节点应该在树y上,直接修改,之后发现它的右子树比它更大,必定在树y上,不管就好了,所以递归分裂左子树
        if(v(now)<=num) x=now,split(rs(now),num,rs(now),y);//镜像情况
        upd(now);
    }
    void merge(llt &now,llt x,llt y)
    {
        if(x==0)    {now=y;return;}
        if(y==0)    {now=x;return;}//这个可以合并写
        if(tree[x].rd>tree[y].rd)   merge(rs(x),rs(x),y),upd(now=x);//和合并很像,因为堆性质,rd大的做根,如果小的rd大,它左子树直接继承,递归合并右子树
        else                        merge(ls(y),x,ls(y)),upd(now=y);
    }
    void insert(llt &now,llt nnd)
    {
        llt ltree,rtree,is;
        split(now,nnd-1,ltree,rtree);  //没啥好说的,直接分裂成两棵,合并的时候把新节点当成一棵树一起合并进去就好
        merge(now,ltree,create(nnd)),merge(now,now,rtree);
    }
    void remove(llt &now,llt num)
    {
        llt ltree,rtree,is;
        split(now,num-1,ltree,rtree);split(rtree,num,is,rtree);
        merge(is,ls(is),rs(is));merge(now,ltree,is);merge(now,now,rtree);//跟插入一样的道理
    }
    llt check_rk(llt now,llt num)
    {
        llt ltree,rtree,ans;
        split(now,num-1,ltree,rtree);//把小于num的值分裂出去,之后直接统计小于num的值个数就好了
        ans=tree[ltree].siz+1;
        merge(now,ltree,rtree);
        return ans;
    }
    llt check_num(llt now,llt rk)
    {
        while(1)
        {
            if(tree[ls(now)].siz>=rk)  now=ls(now);
            else if(tree[ls(now)].siz+tree[now].cnt>=rk)    break;
            else rk-=tree[ls(now)].siz+tree[now].cnt,now=rs(now);
        }
        return v(now);//因为没写按子树大小分裂,所以是正常写法
    }
    llt Pre(llt &now,llt num)
    {
        llt ltree,rtree,is;
        split(now,num-1,ltree,rtree),is=ltree;
        while(rs(is))   is=rs(is);//左子树上全小于num,找最大的就好了
        merge(now,ltree,rtree);
        return v(is);
    }
    llt Next(llt &now,llt num)
    {
        llt ltree,rtree,is;
        split(now,num,ltree,rtree),is=rtree;
        while(ls(is))   is=ls(is);
        merge(now,ltree,rtree);
        return v(is);
    }
}treap;
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    srand(time(0));
    scanf("%lld",&m);
    while(m--)
    {
        scanf("%lld%lld",&op,&x);
        switch(op)
        {
            case 1:{treap.insert(root,x);break;}
            case 2:{treap.remove(root,x);break;}
            case 3:{printf("%lld\n",treap.check_rk(root,x));break;}
            case 4:{printf("%lld\n",treap.check_num(root,x));break;}
            case 5:{printf("%lld\n",treap.Pre(root,x));break;}
            case 6:{printf("%lld\n",treap.Next(root,x));break;}
        }
    }
    return 0;
}

Splay



#include<bits/stdc++.h>
using namespace std;
#define N 250100 
#define llt long long
llt root,m,op,x;
struct SPLAY
{
    llt fa[N],son[N][2],cnt[N],val[N],siz[N],sz;
    #define fson(now) (son[fa[now]][0]!=now)//判断是哪个儿子
    #define upd(now)   siz[now]=siz[son[now][0]]+cnt[now]+siz[son[now][1]]
    void rotate(llt now)//注意这里和treap不一样,函数参数是旋转前的儿子
    {//因为splay的双旋要记录父亲,所以比较长
        bool dir=fson(now);llt is=son[now][dir^1],mid=fa[now];
        son[fa[mid]][fson(mid)]=now;
        son[now][dir^1]=mid;
        son[mid][dir]=is;
        fa[now]=fa[mid],fa[mid]=now,fa[is]=mid;//记得更新父亲
        upd(mid),upd(now);
    } 
    void splay(llt now,llt to)//to是要转到的父亲,to是0就是转到根,一般都需要转到根
    {
        while(fa[now]!=to)//如果转到目标位置结束循环
        {
            llt u=fa[now],v=fa[u];//父亲和祖父
            if(v!=to)
            {
                if(fson(now)==fson(u))    rotate(u);//分讨,如果当前节点的父亲和祖父三点一线,先把父亲转上去
                else                      rotate(now);//否则直接旋转两次当前节点
            }
            rotate(now);
        }
        if(!to) root=now;
    }//splay和rotate是核心部分
    llt find(llt now,llt value)
    {
        while(now&&val[now]!=value) now=son[now][value>val[now]];
        if(now==0)  return 0;
        else    {splay(now,0);return now;} //记住所有操作完都要splay一下
    }
    void insert(llt now,llt value)
    {
        llt fnow=0;
        while(now&&val[now]!=value) fnow=now,now=son[now][value>val[now]];
        if(now) {cnt[now]++;siz[now]++;splay(now,0);return;}  
        else    
        {
            now=++sz;cnt[now]=siz[now]=1;val[now]=value;
            if(fnow!=0) son[fnow][value>val[fnow]]=now;
            fa[now]=fnow;
        }
        splay(now,0);
    }
    llt Nxt(llt now,bool dir)//dir是0就是找前驱,1是找后继,注意这里参数now是节点,返回的也是节点编号
    {
        splay(now,0);//把这个节点转到根,它就没有父亲了,考虑子树即可
        llt u=son[now][dir];
        while(son[u][dir^1])  u=son[u][dir^1];
        return u;
    }
    void remove(llt now,llt value)
    {
        llt u=find(now,value),pr=0,nx=0;
        if(u==0)    return;
        if(cnt[u]>1)   {cnt[u]--,siz[u]--;return;};
        pr=Nxt(u,0),nx=Nxt(u,1);//注意这里比较麻烦,需要把前驱后继转到一起之后找节点
        splay(pr,0);splay(nx,pr);//就是因为这个所以有哨兵节点
        son[nx][0]=0;
    }
    llt check_rk(llt now,llt value){llt us=find(now,value);return siz[son[us][0]];}//直接转到根即可,因为有哨兵节点,所以+1,和-1相互抵消了
    llt check_num(llt now,llt rk)//正常写
    {
        rk++;//注意这里有哨兵节点,所以++
        while(1)
        {
            if(rk>cnt[now]+siz[son[now][0]])   rk=rk-siz[son[now][0]]-cnt[now],now=son[now][1];
            else if(rk<=siz[son[now][0]])      now=son[now][0];
            else {splay(now,0);return val[now];}
        }
    }
}splay;
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif 
    scanf("%lld",&m);
    splay.insert(root,1e18);splay.insert(root,-1e18);//防止删除越界,即找不到前驱,加上两个哨兵节点
    while(m--)
    {
        scanf("%lld%lld",&op,&x);
        switch (op)
        {
            case 1:splay.insert(root,x);break;
            case 2:splay.remove(root,x);break;
            case 3:splay.insert(root,x);printf("%lld\n",splay.check_rk(root,x));splay.remove(root,x);break;//这里3 5 6操作均是对节点,所以不能处理不存在查询节点情况,插入之后再删掉就好了
            case 4:printf("%lld\n",splay.check_num(root,x));break;
            case 5:splay.insert(root,x);printf("%lld\n",splay.val[splay.Nxt(splay.find(root,x),0)]);splay.remove(root,x);break;
            case 6:splay.insert(root,x);printf("%lld\n",splay.val[splay.Nxt(splay.find(root,x),1)]);splay.remove(root,x);break;
        }
    }
    return 0;
}
posted @ 2024-04-28 15:38  wang54321  阅读(43)  评论(2编辑  收藏  举报