BZOJ3224 普通平衡树 [非旋Treap学习笔记]

相比其他平衡树,非旋Treap看起来更加简洁,而且也去掉了玄学的rotate,并加入了merge(合并),split(分裂)

我们看一下非旋Treap的基本操作

1.split(分裂)

分裂操作是按照权值的大小,将一棵树分裂成<=val和>val两棵树,这样可以实现许多操作

inline void split(int now,int k,int &x,int &y)
{
    if (!now) x=y=0;//叶子节点
    else 
    {
        if (val[now]<=k)//将主树与右子树分离 
            x=now,split(rs[now],k,rs[now],y);
        else            //将主树与左子树分离 
            y=now,split(ls[now],k,x,ls[now]);
        update(now);
    }
    return ;
}

2.merge(合并)

我们按照分裂的思路,把两棵树合并到一起,也是和普通平衡树一样,按照根节点的rand值,决定如何合并两棵树

inline int merge(int x,int y)
{
    if (!x||!y) return x+y;
    if (rd[x]<rd[y])
    {
        rs[x]=merge(rs[x],y);
        return update(x),x;
    }//将y树与x的右儿子合并
    else 
    {
        ls[y]=merge(x,ls[y]);
        return update(y),y;
    }//将x树与y的左儿子合并
    return 0;
}

3.newnode

加入新节点的操作就用到了上面两个操作的结合

我们以要插入的点的权值val为分界线,将树划分成两棵树,那么一棵树(左树)中的点的权值都<=val,另一棵树(右树)都>val

那么将新节点看做一棵树,先和左树合并,显然符合平衡树性质,然后将这棵新树和右树合并,这样就得到了一棵nice的新树

据说和普通Treap形态是一样的

inline int newnode(int x)
{
    siz[++sz]=1;val[sz]=x;rd[sz]=rand();
    return sz;
}//加入新节点    
    split(rt,a,x,y);//以a为界限,划分为x和y两棵树 
    rt=merge(merge(x,newnode(a)),y);//先将a看做一棵树与x合并,再和y合并

4.删除节点

删除节点也用到了分裂和合并,我们以要删除的点的权值为分界,划分成两棵树,大于节点权值的点都在右树,左树上都小于等于a的权值,之后将左树按照a的权值-1再划分一次,新右树的根节点就是a,然后合并他的左右儿子,他就被删除了,然后将这三棵树合并,就得到了一棵nice的删除了a的新树


    split(rt,a,x,z);split(x,a-1,x,y);//将树按a划分成两棵树,这时a在x树上,且为最大值 && 将x树按a-1划分成两棵树,这时a一定是y树的根节点 
    y=merge(ls[y],rs[y]);//然后合并a的左右儿子,那么a就被删除了 
    rt=merge(merge(x,y),z);//然后合并三棵树 

5.查询权值a排名

根据刚刚删除节点的操作,我们按a-1将树划分成两棵树,那么左树中的最大值小于等于a-1,右树大于等于a,那么a的排名就是左树的siz+1

{
    split(rt,a-1,x,y);
    printf("%d\n",siz[x]+1);
    rt=merge(x,y);
}//查询排名,按a-1划分成两棵树,x树中的都小于a,所以是x树的节点数+1

6.查询排名为k的权值

这个操作就和普通Treap一样了,根据Treap的性质递归实现

先判断k与x的左子树的大小关系,小于等于说明在左子树,k=左子树+1,那么x就是ans,否则递归到右子树

inline int kth(int now,int k)
{
    while (1)
    {
        if (k<=siz[ls[now]]) now=ls[now];      //在左子树中
        else if (k==siz[ls[now]]+1) return now;//get到答案 
        else k-=siz[ls[now]]+1,now=rs[now];    //在右子树中 
    }
    return 0;
}
    printf("%d\n",val[kth(rt,a)]);

7.查询a的前驱

用到了排名查询和分裂,先按a-1将树分为两棵树,那么左树中的权值都小于a,那么左树中最大的就是a的前驱,也就是左树中排名为左树大小的元素

    split(rt,a-1,x,y);//先按a-1划分成两棵树,x树中的数都小于等于a-1,那么排名为siz[x]的数就是最大的 
    printf("%d\n",val[kth(x,siz[x])]);
    rt=merge(x,y);

8.查询a的后继

原理和前驱相同,按照a将树划分成两棵树,那么右树中最小的就是a的后继,也就是右树中排名为1的元素

    split(rt,a,x,y);//原理同前驱
    printf("%d\n",val[kth(y,1)]);
    rt=merge(x,y);

整体代码(数组版,比较简洁,直接封装到struct也可以)

//Treap By AcerMo
#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=100500;
int n,sz;
int ls[M],rs[M],val[M],rd[M],siz[M];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline void update(int x)
{
    siz[x]=siz[ls[x]]+1+siz[rs[x]];
    return ;
}
inline int newnode(int x)
{
    siz[++sz]=1;val[sz]=x;rd[sz]=rand();
    return sz;
}//加入新节点 
inline int merge(int x,int y)
{
    if (!x||!y) return x+y;
    if (rd[x]<rd[y])
    {
        rs[x]=merge(rs[x],y);
        return update(x),x;
    }//将y树与x的右儿子合并
    else 
    {
        ls[y]=merge(x,ls[y]);
        return update(y),y;
    }//将x树与y的左儿子合并
    return 0;
}
inline void split(int now,int k,int &x,int &y)
{
    if (!now) x=y=0;//叶子节点
    else 
    {
        if (val[now]<=k)//将主树与右子树分离 
            x=now,split(rs[now],k,rs[now],y);
        else            //将主树与左子树分离 
            y=now,split(ls[now],k,x,ls[now]);
        update(now);
    }
    return ;
}
inline int kth(int now,int k)
{
    while (1)
    {
        if (k<=siz[ls[now]]) now=ls[now];      //在左子树中
        else if (k==siz[ls[now]]+1) return now;//get到答案 
        else k-=siz[ls[now]]+1,now=rs[now];    //在右子树中 
    }
    return 0;
}
signed main()
{
    srand((unsigned)time(NULL));//这个不能忘 
    n=read();int rt=0,x,y,z;
    for (int i=1;i<=n;i++)
    {
        int fl=read(),a=read();
        if (fl==1)
        {
            split(rt,a,x,y);//以a为界限,划分为x和y两棵树 
            rt=merge(merge(x,newnode(a)),y);//先将a看做一棵树与x合并,再和y合并 
        }//加入新节点 
        else if (fl==2)
        {
            split(rt,a,x,z);split(x,a-1,x,y);//将树按a划分成两棵树,这时a在x树上,且为最大值 && 将x树按a-1划分成两棵树,这时a一定是y树的根节点 
            y=merge(ls[y],rs[y]);//然后合并a的左右儿子,那么a就被删除了 
            rt=merge(merge(x,y),z);//然后合并三棵树 
        }//删除节点
        else if (fl==3)
        {
            split(rt,a-1,x,y);
            printf("%d\n",siz[x]+1);
            rt=merge(x,y);
        }//查询排名,按a-1划分成两棵树,x树中的都小于a,所以是x树的节点数+1
        else if (fl==4)
        {
            printf("%d\n",val[kth(rt,a)]);
        }//查询排名为a的数 
        else if (fl==5)
        {
            split(rt,a-1,x,y);//先按a-1划分成两棵树,x树中的数都小于等于a-1,那么排名为siz[x]的数就是最大的 
            printf("%d\n",val[kth(x,siz[x])]);
            rt=merge(x,y);
        }//查询x的前驱
        else if (fl==6)
        {
            split(rt,a,x,y);//原理同前驱
            printf("%d\n",val[kth(y,1)]);
            rt=merge(x,y);
        }//查询x的后继
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ACerAndAKer/article/details/81570786