非旋Treap详解

(入门请按顺序一边做例题一边看下去,题目之间还有东西)

非旋Treap 平衡树的一种

个人认为是最好用的平衡树,第一支持可持久化,第二代码短,第三操作方法直观

下面看平衡树基础的操作:


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

  1. 插入x数

  2. 删除x数(若有多个相同的数,因只删除一个)

  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

  4. 查询排名为x的数

  5. 求x的前驱(前驱定义为小于x,且最大的数)

  6. 求x的后继(后继定义为大于x,且最小的数


首先  Treap的定义我以前一篇旋转Treap中就有提到,BST+Heap

BST保证有序,Heap保证不容易被卡 关于定义见旋转Treap的博客


首先,非旋Treap有两个基础操作——Split和Merge

Split操作:把一个Treap拆成两个Treap,第一个Treap的节点在第二个Treap之前(BST遍历中) 

Merge操作:把两个Treap合并成一个  新的Treap中,前一个Treap的遍历顺序都在后一个前面


Tip:常用的还有一个Kth ,即在一个Treap中找到第K大(常数较小写法,暴力的话可以用Split直接实现)

Tip:Split操作有两种  第一种  把权值<=v的分在第一个Treap,第二种  把前k个分在第一个Treap(较常用)


先不考虑这两个东西怎么写,我们来用这两个东西解决上面6个操作

我们按照x的大小为权来建Treap

1、插入x

把小于x的分裂成一个子树P1,大于等于x的为P2,新建节点Node

然后把P1和Node合并,再把得到的子树与P2合并

2、删除x

把小于x的分裂成P1,大于等于x的为P2

把P2中大于X的分裂成P3

合并P1,P3

3、小于x分裂成P1,输出siz[P1]+1

4、Kth

5、小于x的分裂成P1,用Kth找到P1的最大值

6、大于x的分裂成P2,Kth


基本上这些操作都是Split和Merge乱搞。。不用死记,理解成拆线段即可


下面来讲一下Split和Merge的实现

先丢个代码。。

Split:

inline void Split(int x,int k,int &p1,int &p2)
{
    if(x==0)	p1=p2=0;
    else
    {
        if(siz[son[x][0]]

k就是前k个,类似于权值线段树找第k大的写法

x是待分配节点,p1,p2是两棵子树中待接收的节点

即每次递归是一个把x分配给p1或p2的过程

p1是向左偏的,具体的话。。我安利一下远航之曲的博客中的动图,大家自己搜一下


Merge:

inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    if(rnd[x]

rnd是Treap中的随机权值,决定树的结构

这里我要说一下。。。rnd这个东西维不维护成堆结构   并没有关系

意会一下,rnd[x]

但是rand()常数较大,所以一开始就处理出rnd数组

为什么要这么说?

因为后面的Build不能满足堆性质


扯远了,回到Merge

Merge操作就类似于动态开点线段树的合并,或者说更类似于左偏树

……不想讲了


然后是Kth的写法

和线段树也差不多,就是要注意,当前的根节点也是一个实点,平衡树的节点数就是标准的n个

。。写成非递归写法

inline int Kth(int x,int k)
{
    while(1)
    {
        if(siz[son[x][0]]>=k)	x=son[x][0];
        else	if(siz[son[x][0]]+1==k)	return val[x];
        else	k-=siz[son[x][0]]+1,x=son[x][1]; 
    }
}

不知道大家有没有好奇Split和Merge中那个Upd是啥

Upd就是类似于线段树中的pushup

也就是从子节点来更新父节点

一般的话只是siz的维护,但是对于一些。。恶心的题,Upd会非常的长

具体见bzoj 1500


还有一个是Build操作

满足Heap的也可以写,但是感觉比较难写,维护这个Heap并没有什么鸟用,就不讲了

不维护Heap性质的一种写法:

inline int Build(int l,int r)
{
    int mid=l+r>>1;
    int tmp=New(a[mid]);
    if(l<=mid-1)	son[tmp][0]=Build(l,mid-1);
    if(mid+1<=r)	son[tmp][1]=Build(mid+1,r);
    Upd(tmp);
    return tmp;
}


然后来一些例题吧还是,个人认为很多操作不做例题自己想还是很难想出来的

一个是上面那个题  luogu3369普通平衡树

就不讲了

。。第一个代码写的好挫啊

#include
#include
#include
#include
#include
#include
#include
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
using namespace std;
inline ll read()
{
    ll t=0,f=1;char c=getchar();
    while(c<'0'||c>'9')   {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') t=t*10+c-48,c=getchar();
    return t*f;
}
int tot,siz[500001],val[500001],rnd[500001],son[500001][2],p1,p2,opt,x,n,rt;
inline void upd(int x)	{siz[x]=1+siz[son[x][0]]+siz[son[x][1]];}
inline int ne(int x){siz[++tot]=1;val[tot]=x;rnd[tot]=rand();return tot;}
inline void split(int now,int x,int &p1,int &p2)
{
    if(!now)	p1=p2=0;
    else
    {
        if(val[now]<=x)	
            p1=now,split(son[now][1],x,son[p1][1],p2);
        else	
            p2=now,split(son[now][0],x,p1,son[p2][0]);	
        upd(now);
    }

}
inline int merge(int x,int y)
{
    if(!x||!y)	return x^y;
    if(rnd[x]=k)	x=son[x][0];
    else	if(siz[son[x][0]]+1==k)	return x;
        else	k-=siz[son[x][0]]+1,x=son[x][1];
}
int main()
{
//	freopen("in.in","r",stdin);
    srand(time(0));
    n=read();
    For(i,1,n)
    {
        opt=read();x=read();
        if(opt==1)
        {
            split(rt,x,p1,p2);
            rt=merge(merge(p1,ne(x)),p2);
        }
        if(opt==2)
        {
            int p3=0;
            split(rt,x,p1,p2);
            split(p1,x-1,p1,p3);
            p3=merge(son[p3][0],son[p3][1]);
            rt=merge(merge(p1,p3),p2);
        }
        if(opt==3)
        {
            split(rt,x-1,p1,p2);
            printf("%d\n",siz[p1]+1);
            rt=merge(p1,p2);
        }
        if(opt==4)
            printf("%d\n",val[kth(rt,x)]);
        if(opt==5)
        {
            split(rt,x-1,p1,p2);
            printf("%d\n",val[kth(p1,siz[p1])]);
            rt=merge(p1,p2);
        }
        if(opt==6)
        {
            split(rt,x,p1,p2);
            printf("%d\n",val[kth(p2,1)]);
            rt=merge(p1,p2);
        }
    }
}

然后是区间翻转的板子题 luogu3391  文艺平衡树

题意:区间翻转,输出最终序列

引入lazy标记,和线段树类似,下传

最后Dfs输出

。。还是好挫

#include
#include
#include
#include
#include
#include
#include
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
using namespace std;
inline ll read()
{
    ll t=0,f=1;char c=getchar();
    while(c<'0'||c>'9')   {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') t=t*10+c-48,c=getchar();
    return t*f;
}
int tot,siz[500001],val[500001],rnd[500001],son[500001][2],p1,p2,opt,x,n,rt,tag[500001],m,l,r;
inline int ne(int x){siz[++tot]=1;val[tot]=x;rnd[tot]=rand();return tot;}
inline void upd(int x){siz[x]=1+siz[son[x][1]]+siz[son[x][0]];}
inline void Build(int &x,int l,int r)
{
    if(l>r)	return;
    int mid=l+r>>1;
    x=ne(mid);
    if(l==r)	return;
    Build(son[x][0],l,mid-1);
    Build(son[x][1],mid+1,r);
    upd(x);
}
inline void push(int x)
{
    if(tag[x]&&x)
    {
        swap(son[x][1],son[x][0]);
        if(son[x][1])	tag[son[x][1]]^=1;
        if(son[x][0])	tag[son[x][0]]^=1;
        tag[x]=0;
    }
}
inline void split(int x,int k,int &p1,int &p2)//p1和p2待填充  分配x 
{
    if(!x)	p1=p2=0;
    else
    {
        push(x);
        if(siz[son[x][0]]>=k)	p2=x,split(son[x][0],k,p1,son[p2][0]);
        else	p1=x,split(son[x][1],k-siz[son[x][0]]-1,son[p1][1],p2);
        upd(x);
    }
}
inline int merge(int x,int y)
{
    if(!x||!y)	return x^y;
    push(x);push(y);
    if(rnd[x]=1&&val[x]<=n)printf("%d ",val[x]);
    if(son[x][1])	dfs(son[x][1]);	
}
int main()
{
    n=read();m=read();
    Build(rt,1,n+2);
    For(i,1,m)
    {
        l=read();r=read();
        R(l,r);
    }
    dfs(rt);
}

练习:luogu1486 郁闷的出纳员

#include
#include
#include
#include
#include
#include
#include
#include
#define For(i,j,k)  for(register int i=j;i<=k;++i)
#define Dow(i,j,k)  for(register int i=k;i>=j;--i)
#define ll long long
#define mk make_pair
#define pb push_back
#define eps 1e-8
#define pa pair
#define fir first
#define sec second
using namespace std;
inline ll read()
{
    ll t=0,f=1;char c=getchar();
    while(c<'0'||c>'9')   {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') t=t*10+c-48,c=getchar();
    return t*f;
}
int siz[200001],rad[200001],v[200001],n,mi,son[200001][2],tot,rt,x,y,z,del,ans;
char s[5];
inline void Upd(int x)	{siz[x]=1+siz[son[x][0]]+siz[son[x][1]];}
inline int New(int x){siz[++tot]=1;v[tot]=x;rad[tot]=rand();return tot;}
inline void Split(int now,int x,int &p1,int &p2)
{
    if(!now)	p1=p2=0;
    else
    {
        if(v[now]<=x)	
            p1=now,Split(son[now][1],x,son[p1][1],p2);
        else	
            p2=now,Split(son[now][0],x,p1,son[p2][0]);	
        Upd(now);
    }

}
inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    if(rad[x]=k)	x=son[x][1];
    else	if(siz[son[x][1]]+1==k)	return v[x];
        else	k-=siz[son[x][1]]+1,x=son[x][0];
}
int main()
{
    srand(time(0));
    n=read();mi=read();
    For(i,1,n)
    {
        scanf("\n%s",s+1);
        if(s[1]=='I')
        {
            int tv=read()-del;
            if(tvsiz[rt])	puts("-1");else 
                printf("%d\n",Query(rt,tv)+del);
        }
    }
    cout<

例题 bzoj1500/luogu2042 维护数列

恶心的题。。题意不说了

首先要理解在一个Treap上维护一些标记,l和r的顺序是BST中的顺序

可以理解成线段树上的线段

然后这道题就是码码码

Upd中维护了一大堆标记

、、注意最后一个操作至少选一个数字2333

#include
#include
#include
#include
#include
#include
#include
#include
#define For(i,j,k)	for(int i=j;i<=k;++i)
#define Dow(i,j,k)	for(int i=k;i>=j;--i)
#define ll long long
#define inf 1e9
using namespace std;
inline int read()
{
    int t=0,f=1;char c=getchar();
    while(!isdigit(c))	{if(c=='-')	f=-1;c=getchar();}
    while(isdigit(c))	t=t*10+c-'0',c=getchar();
    return t*f;
}
queue Tr;
const int N=550001;
int lmx[N],rmx[N],mx[N],sum[N],v[N],siz[N],son[N][2],tag[N],Ctag[N],rnd[N];
char s[50];
int p1,p2,p3,n,m,rt,tot,x,t,len;
inline void Rev(int x)
{
    tag[x]^=1;
    swap(son[x][1],son[x][0]);swap(lmx[x],rmx[x]);
}
inline void Cover(int x,int tv)
{
    Ctag[x]=tv;sum[x]=tv*siz[x];v[x]=tv;
    lmx[x]=rmx[x]=max(sum[x],0);
    if(v[x]>0)	mx[x]=sum[x];else mx[x]=v[x];
}
inline void Trash(int x)
{
    if(!x)	return;
    Tr.push(x);
    Trash(son[x][1]);Trash(son[x][0]);
}
inline int New(int x)
{
    int tmp=0;
    if(Tr.empty())	tmp=++tot;else tmp=Tr.front(),Tr.pop();	
    son[tmp][1]=son[tmp][0]=tag[tmp]=0;
    lmx[tmp]=rmx[tmp]=max(0,x);sum[tmp]=v[tmp]=mx[tmp]=x;siz[tmp]=1;Ctag[tmp]=2000;rnd[tmp]=rand();
    return tmp;
}
inline void Push(int x)
{
    if(tag[x])	
    {
        if(son[x][1])Rev(son[x][1]);if(son[x][0])Rev(son[x][0]);
        tag[x]=0;
    }
    if(Ctag[x]!=2000)
    {
        if(son[x][1])Cover(son[x][1],Ctag[x]);if(son[x][0])Cover(son[x][0],Ctag[x]);
        Ctag[x]=2000;
    }
}
inline void Upd(int x)
{
    siz[x]=siz[son[x][1]]+siz[son[x][0]]+1;
    sum[x]=sum[son[x][1]]+sum[son[x][0]]+v[x];
    if(son[x][1]&&son[x][0])
    {
        lmx[x]=max(lmx[son[x][0]],sum[son[x][0]]+v[x]+lmx[son[x][1]]);
        rmx[x]=max(rmx[son[x][1]],sum[son[x][1]]+v[x]+rmx[son[x][0]]);
        mx[x]=max(max(mx[son[x][0]],mx[son[x][1]]),rmx[son[x][0]]+lmx[son[x][1]]+v[x]);
    }
    else	
    if(son[x][1])
    {
        lmx[x]=max(0,v[x]+lmx[son[x][1]]);
        rmx[x]=max(rmx[son[x][1]],sum[x]);
        mx[x]=max(mx[son[x][1]],v[x]+lmx[son[x][1]]);
    }
    else
    if(son[x][0])
    {
        lmx[x]=max(lmx[son[x][0]],sum[x]);
        rmx[x]=max(rmx[son[x][0]]+v[x],0);
        mx[x]=max(mx[son[x][0]],rmx[son[x][0]]+v[x]);
    }
    else	lmx[x]=rmx[x]=max(v[x],0),mx[x]=v[x];
}
inline void Split(int x,int k,int &p1,int &p2)
{
    if(!x)	p1=p2=0;
    else
    {
        Push(x);
        if(k>siz[son[x][0]])
            p1=x,Split(son[x][1],k-siz[son[x][0]]-1,son[p1][1],p2);
        else
            p2=x,Split(son[x][0],k,p1,son[p2][0]);
        Upd(x);
    }
}
inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    Push(x);Push(y);
    if(rnd[x]

Treap的合并:

注意这里不是Merge,前一个Treap不保证小于后一个Treap

启发式合并复杂度(log^2)

就把较小的Treap中的节点一个个塞进大Treap即可

inline void To(int &x,int y)
{
    if(!y)	return;
    To(x,son[y][0]);To(x,son[y][1]);
    son[y][0]=son[y][1]=0;
    Ins(x,y);
}
inline void Tog(int x,int y)
{
    if(siz[rt[x]]

例题:luogu3224 永无乡

启发式合并裸题  加个并查集就行

// luogu-judger-enable-o2
#include
#include
#include
#include
#include
#include
#include
#include
#define For(i,j,k)	for(int i=j;i<=k;++i)
#define Dow(i,j,k)	for(int i=k;i>=j;--i)
#define ll long long
#define inf 1e9
#define min(x,y)	((x)<(y)?(x):(y))
#define max(x,y)	((x)>(y)?(x):(y))
using namespace std;
inline int read()
{
    int k=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}
    return k*f;
}
inline void write(int x){if(x<0)	{putchar('-');write(-x);return;}if(x>=10)	write(x/10);putchar(x%10+'0');}
inline void writeln(int x){write(x);puts("");}
int F[200001],siz[200001],rt[200001],num[200001],tot,rnd[200001],val[200001],son[200001][2];
int n,m,q,x,y,p1,p2,p3,a[200001];
char opt[5];
inline int Get(int x){return x==F[x]?x:F[x]=Get(F[x]);}
inline void Upd(int x){siz[x]=siz[son[x][1]]+siz[son[x][0]]+1;}
inline int New(int x,int y){val[++tot]=x;siz[tot]=1;num[tot]=y;rnd[tot]=rand();return tot;}
inline void Split(int x,int v,int &p1,int &p2)
{
    if(!x)	p1=p2=0;
    else
    {
        if(val[x]<=v)	p1=x,Split(son[x][1],v,son[p1][1],p2);
        else	p2=x,Split(son[x][0],v,p1,son[p2][0]);
        Upd(x);	
    }
}
inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    if(rnd[x]=k)	x=son[x][0];
        else	if(siz[son[x][0]]+1==k)	return x;
        else	k-=siz[son[x][0]]+1,x=son[x][1];
    }
}
int main()
{
    n=read();m=read();
    For(i,1,n)	a[i]=read(),F[i]=i;
    For(i,1,m)
    {
        x=read();y=read();
        if(Get(x)!=Get(y))	F[Get(x)]=Get(y);
    }
    For(i,1,n)	
    {
        if(!rt[Get(i)])	rt[Get(i)]=New(a[i],i);
        else	Ins(rt[Get(i)],New(a[i],i));
    }
    q=read();
    For(i,1,q)
    {
        scanf("\n%s",opt+1);
        if(opt[1]=='B')
        {
            x=read();y=read();
            if(Get(x)!=Get(y))
                Tog(Get(x),Get(y));
        }
        else
        {
            x=read();y=read();
            if(y<1||y>siz[rt[Get(x)]])	puts("-1");else writeln(num[Kth(rt[Get(x)],y)]);
        }
    }
}


下面还有一种基础操作,就是在Treap中查找比一个节点大/小的节点有多少个

建议写递归版,或者栈模拟

直接往上跳不支持lazy标记下传

下面是例题

luogu2596

//这题我写了往上跳的版本,下一题写的递归版(Get函数)

#include
#include
#include
#include
#include
#define For(i,j,k)	for(int i=j;i<=k;++i)
#define	Dow(i,j,k)	for(int i=k;i>=j;--i)
#define ll long long
using namespace std;
inline int read()
{
    int t=0,f=1;char c=getchar();
    while(!isdigit(c))	{if(c=='-')	f=-1;c=getchar();}
    while(isdigit(c))	t=t*10+c-'0',c=getchar();
    return t*f;
}
const int N=1000001;
int tot,siz[N],son[N][2],val[N],rnd[N],a[N],pos[N],Fa[N],rt,x;
int n,m,p1,p2,p3,p4;
inline int New(int x){siz[++tot]=1;son[tot][1]=son[tot][0]=0;pos[x]=tot;val[tot]=x;Fa[x]=0;rnd[tot]=rand();return tot;}
inline void Upd(int x){siz[x]=siz[son[x][1]]+siz[son[x][0]]+1;if(son[x][1])	Fa[son[x][1]]=x;if(son[x][0])	Fa[son[x][0]]=x;}
inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    if(rnd[x]=k)	x=son[x][0];
        else	if(siz[son[x][0]]+1==k)	return val[x];
        else	k-=siz[son[x][0]]+1,x=son[x][1]; 
    }
}
inline int Get(int x)
{
    int tmp=siz[son[x][0]]+1;
    while(1)
    {
        if(x==son[Fa[x]][1])
            tmp+=siz[son[Fa[x]][0]]+1;
        x=Fa[x];
        
        if(x==rt||!x)	return tmp;
    }
}
char opt[50];
int main()
{
    n=read();m=read();
    For(i,1,n)	a[i]=read();
    For(i,1,n)	rt=Merge(rt,New(a[i]));
    For(i,1,tot)	Upd(i);
    For(i,1,m)
    {
        scanf("\n%s",opt+1);
        if(opt[1]=='T')
        {
            x=read();
            int tmp=Get(pos[x])-1;
            Split(rt,tmp,p1,p2);
            Split(p2,1,p2,p3);
            rt=Merge(Merge(p2,p1),p3);
        }
        if(opt[1]=='B')
        {
            x=read();
            int tmp=Get(pos[x])-1;
            Split(rt,tmp,p1,p2);
            Split(p2,1,p2,p3);
            rt=Merge(Merge(p1,p3),p2);	
        }
        if(opt[1]=='I')
        {
            int x=read(),y=read();
            int w,v,t,z,i1,i2;
            if(y){
                int u=Get(pos[x]);
                Split(rt,u-1,w,v);
                Split(v,1,t,z);
                if(y==-1){
                    Split(w,u-2,i1,i2);
                    rt=Merge(Merge(Merge(i1,t),i2),z);
                }else{
                    Split(z,1,i1,i2);
                    rt=Merge(Merge(Merge(w,i1),t),i2);
                }
            }
        }
        if(opt[1]=='A')
        	printf("%d\n",Get(pos[read()])-1);
        if(opt[1]=='Q')	
        	printf("%d\n",Kth(rt,read()));
    }
}

P3165 机械臂排序

#include
#include
#include
#include
#include
#define For(i,j,k)	for(int i=j;i<=k;++i)
#define	Dow(i,j,k)	for(int i=k;i>=j;--i)
#define ll long long
using namespace std;
inline int read()
{
    int t=0,f=1;char c=getchar();
    while(!isdigit(c))	{if(c=='-')	f=-1;c=getchar();}
    while(isdigit(c))	t=t*10+c-'0',c=getchar();
    return t*f;
}
const int N=200001;
int tot,siz[N],son[N][2],val[N],rnd[N],pos[N],Fa[N],rt,x;
int n,m,p1,p2,p3,p4,tag[N],a[N],to[10000001];
inline int New(int x){siz[++tot]=1;son[tot][1]=son[tot][0]=0;pos[x]=tot;val[tot]=x;Fa[x]=0;rnd[tot]=rand();return tot;}
inline void Upd(int x){siz[x]=siz[son[x][1]]+siz[son[x][0]]+1;if(son[x][1])	Fa[son[x][1]]=x;if(son[x][0])	Fa[son[x][0]]=x;}
inline void Rev(int x){tag[x]^=1;swap(son[x][1],son[x][0]);}
inline void Push(int x){if(tag[x])	{tag[x]=0;Rev(son[x][1]);Rev(son[x][0]);}}
inline int Merge(int x,int y)
{
    if(!x||!y)	return x^y;
    Push(x);Push(y);
    if(rnd[x]
大概就是这样了。。可持久化Treap还不会啊留坑待填

你可能感兴趣的:(数据结构)