旋转还是无旋?treap从入门到想死

旋转treap

优点:代码比splay好打
缺点:很多操作(尤其是区间操作)不资磁

插入

插入方式和二叉查找树一致,新建节点后,要给节点rand一个pos值。
然后返回改节点的父亲,如果该节点pos值比父亲大,就要旋转,以保证父节点的pos值小于儿子的,满足堆的性质。使用随机数可以使得这棵树尽量平衡。

void ins(int &x,LL bh) {
	if(!x) {x=++sss,pos[x]=rand(),num[x]=bh,v[x]=1;return;}
	if(bh<num[x]) {ins(son[x][0],bh);if(pos[son[x][0]]<pos[x]) spin(x,0);}
	else {ins(son[x][1],bh);if(pos[son[x][1]]<pos[x]) spin(x,1);}
}

旋转

treap的旋转操作比splay清爽多了,可以说是相当于splay的zig/zag操作。

void spin(int &x,int is) {
	int t=son[x][is];
	son[x][is]=son[t][!is],son[t][!is]=x,x=t;
}

查找前驱后继

例题:洛谷P2234/bzoj2234/codevs1296 营业额统计

LL pre(int x,LL num) {//前驱
	if(!x) return -inf;
	if(v[x]>num) return pre(son[x][0],num);
	return max(pre(son[x][1],num),v[x]);
}
LL nxt(int x,LL num) {//后继
	if(!x) return inf;
	if(v[x]<num) return nxt(son[x][1],num);
	return min(nxt(son[x][0],num),v[x]);
}

## 删除
找到要删除的节点x,看它的左右儿子,旋转pos值较小的那个儿子,直到x成为叶子节点,删除x。

void del(int &x,LL bh) {
	if(!x) return;
	if(num[x]==bh) {
		if(son[x][0]*son[x][1]==0) {x=son[x][0]+son[x][1];return;}
		if(pos[son[x][0]]<pos[son[x][1]]) spin(x,0),del(x,bh);
		else spin(x,1),del(x,bh);
	}
	if(bh<num[x]) del(son[x][0],bh);
	else del(son[x][1],bh);
}

无旋treap

优点:可以可持久化,资磁的操作多
缺点:比splay慢
例题:洛谷P2464/codevs1840
打这道题的时候,我真的是比小j还烦恼…这道题有一种解法是离散+对于每一种书建立一棵无旋treap,然后进行操作。
无旋treap是基于合并与分裂两个操作的一种treap。

合并

现在我们有两棵分别以a和b为根的treap,想要a在左b在右的合并。如何合并呢?
首先,比较a和b的pos值,较小的那个可以作为一个根,然后将其左/右子树和另一个合并。如果是a,那么让a的右子树和b合并。如果是b,那么让b的左子树和a合并。

int merge(int a,int b) {
	if(!a) return b;
	if(!b) return a;
	if(pos[a]<pos[b]) {son[a][1]=merge(son[a][1],b),up(a);return a;}
	else {son[b][0]=merge(a,son[b][0]),up(b);return b;}
}

分裂

现在我们想在以x为根的子树里,左边分裂出一个num大小的树。怎么办?
我们的函数返回值是一个pair,这样可以存两棵分裂出来的子树的根:左边的和右边的。
首先特判两种可以直接分裂的情况:
1.x的左子树大小为num,此时可以直接分裂为左子树和以x为根的子树
2.x的左子树大小为num-1,此时可以直接分裂为以x为根的子树和右子树
然后再比较x左子树和num的大小,进行递归分裂。(不懂看代码)

#define pr pair<int,int>
#define mkp make_pair
pr split(int x,int num) {//从将树左边分离出一个num大小的
	if(!x) return mkp(0,0);
	int ls=son[x][0],rs=son[x][1];
	if(sz[son[x][0]]==num) {son[x][0]=0,up(x);return mkp(ls,x);}
	if(sz[son[x][0]]+1==num) {son[x][1]=0,up(x);return mkp(x,rs);}
	if(num<sz[son[x][0]]) {//递归分裂
		pr tmp=split(son[x][0],num);
		son[x][0]=tmp.second,up(x);
		return mkp(tmp.first,x);
	}
	else {
		pr tmp=split(son[x][1],num-sz[son[x][0]]-1);
		son[x][1]=tmp.first,up(x);
		return mkp(x,tmp.second);
	}
}

插入

有了上面两种操作,插入操作就变得轻松了许多,即新建一个节点,找到原树中该节点应该排在第几,然后分裂原来的树,再依次合并(分裂出来的左边,新节点,分裂出来的右边)
插入一个区间也与之类似。
代码:见完整代码标记处。

删除

找到要删除的节点,先分裂出该节点左边一棵子树,再分裂出该节点右边一棵子树,然后把两棵子树合并,就不要那个节点了。
删除一个区间也与之类似。
代码:见完整代码标记处。

区间操作

用类似于删除的方法把整个区间取出来,打上区间操作标记即可。
然后在合并和删除操作的时候都要记得pushdown。
例题就是bzoj3223/洛谷P3391 文艺平衡树 啦,完整代码下面有

完整代码

小J的烦恼

#include<bits/stdc++.h>
using namespace std;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
#define pr pair<int,int>
#define mkp make_pair
const int N=600005;
int n,m,cnt,sss;
map<int,int> mp;
int rt[N],bk[N],son[N][2],pos[N],wz[N],sz[N];
int newjd(int x) {if(!mp[x]) mp[x]=++cnt; return mp[x];}
void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
int merge(int a,int b) {
	if(!a) return b;
	if(!b) return a;
	if(pos[a]<pos[b]) {son[a][1]=merge(son[a][1],b),up(a);return a;}
	else {son[b][0]=merge(a,son[b][0]),up(b);return b;}
}
pr split(int x,int num) {//从将树左边分离出一个num大小的
	if(!x) return mkp(0,0);
	int ls=son[x][0],rs=son[x][1];
	if(sz[son[x][0]]==num) {son[x][0]=0,up(x);return mkp(ls,x);}
	if(sz[son[x][0]]+1==num) {son[x][1]=0,up(x);return mkp(x,rs);}
	if(num<sz[son[x][0]]) {
		pr tmp=split(son[x][0],num);
		son[x][0]=tmp.second,up(x);
		return mkp(tmp.first,x);
	}
	else {
		pr tmp=split(son[x][1],num-sz[son[x][0]]-1);
		son[x][1]=tmp.first,up(x);
		return mkp(x,tmp.second);
	}
}
int find(int x,int kth) {//wz小于kth的节点个数
	if(!x) return 0;
	if(wz[x]<=kth) return sz[son[x][0]]+1+find(son[x][1],kth);
	return find(son[x][0],kth);
}
int main()
{
	char ch[10];int x,y,z;
	n=read(),m=read(),srand(m);
	for(int i=1;i<=n;++i) {
		x=read(),bk[i]=newjd(x);
		pos[++sss]=rand(),sz[sss]=1,wz[sss]=i;
		rt[bk[i]]=merge(rt[bk[i]],sss);//这是插入操作
	}
	while(m--) {
		scanf("%s",ch),x=read(),y=read();
		if(ch[0]=='Q') {
			z=read(),z=newjd(z);
			printf("%d\n",find(rt[z],y)-find(rt[z],x-1));
		}
		else {
			pr t1,t2;
			t1=split(rt[bk[x]],find(rt[bk[x]],x)-1);//这里是删除操作
			t2=split(t1.second,1);
			rt[bk[x]]=merge(t1.first,t2.second);
			bk[x]=newjd(y);
			t1=split(rt[bk[x]],find(rt[bk[x]],x));//以下是插入操作
			pos[++sss]=rand(),sz[sss]=1,wz[sss]=x;
			rt[bk[x]]=merge(t1.first,merge(sss,t1.second));
		}
	}
	return 0;
}

文艺平衡树

#include<bits/stdc++.h>
using namespace std;
#define mkp make_pair
#define pr pair<int,int>
const int N=100005;
int n,m,rt;
int son[N][2],rev[N],pos[N],sz[N];
void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
void pd(int x) {
	if(son[x][0]) rev[son[x][0]]^=1;
	if(son[x][1]) rev[son[x][1]]^=1;
	swap(son[x][0],son[x][1]),rev[x]=0;
}
int merge(int a,int b) {
	if(!a) return b;
	if(!b) return a;
	if(rev[a]) pd(a); if(rev[b]) pd(b);//注意pushdown
	if(pos[a]<pos[b]) {son[a][1]=merge(son[a][1],b),up(a);return a;}
	else {son[b][0]=merge(a,son[b][0]),up(b);return b;}
}
pr split(int x,int num) {
	if(!x) return mkp(0,0);
	if(rev[x]) pd(x);//注意pushdown
	int ls=son[x][0],rs=son[x][1];
	if(sz[son[x][0]]==num) {son[x][0]=0,up(x);return mkp(ls,x);}
	if(sz[son[x][0]]+1==num) {son[x][1]=0,up(x);return mkp(x,rs);}
	if(num<sz[son[x][0]]) {
		pr tmp=split(son[x][0],num);
		son[x][0]=tmp.second,up(x);
		return mkp(tmp.first,x);
	}
	else {
		pr tmp=split(son[x][1],num-sz[son[x][0]]-1);
		son[x][1]=tmp.first,up(x);
		return mkp(x,tmp.second);
	}
}
int build(int l,int r) {
	if(l==r) {pos[l]=rand(),sz[l]=1;return l;}
	int mid=(l+r)>>1;
	return merge(build(l,mid),build(mid+1,r));
}
void print(int x) {
	if(rev[x]) pd(x);
	if(son[x][0]) print(son[x][0]);
	printf("%d ",x);
	if(son[x][1]) print(son[x][1]);
}
int main()
{
	int x,y;srand(14233);
	scanf("%d%d",&n,&m);rt=build(1,n);
	while(m--) {
		scanf("%d%d",&x,&y);
		pr t2=split(rt,y);
		pr t1=split(t2.first,x-1);
		rev[t1.second]^=1;
		rt=merge(t1.first,merge(t1.second,t2.second));
	}
	print(rt);
	return 0;
}

维护数列

既然学了无旋treap,就不得不去做一做这道神题啦!
你会发现无旋treap虽然比splay慢,可是编码速度更快呢!
戳我看代码

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值