(入门请按顺序一边做例题一边看下去,题目之间还有东西)
非旋Treap 平衡树的一种
个人认为是最好用的平衡树,第一支持可持久化,第二代码短,第三操作方法直观
下面看平衡树基础的操作:
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
- 求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
例题 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还不会啊留坑待填