码迷,mamicode.com
首页 > 其他好文 > 详细

关于fhq_treap的一点总结

时间:2021-02-01 12:51:28      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:操作   要求   a+b   turn   line   read   date   www   its   

算法原理

\(fhq - treap\)是一种好用的平衡数,以分裂合并为基本操作,代码简洁优雅,能解决包括序列操作在内的大部分问题,适合初学平衡树的\(OIer\)们(比如我)学习和掌握。

核心:分裂与合并

分裂时一般传四个参数

\(now\) :当前节点

\(k\) :以权值分裂或者以排名分裂时对两棵树的要求

$a $ 和 \(b\) :传址调用,用于记录当前点的左/右儿子会变成哪一个

流程(以权值分裂为例,小于\(k\)分到左边(\(a\)这边),大于\(k\)分到右边(\(b\)这边)):

若当前点的权值小于等于我们要求的,说明这个点的左子树上的权值都小于\(k\)

那我们就要到这个点的右子树上去分。那么这个点的右儿子将会改变,传下去。同时这个点也变成了某个点的左/右儿子,修改\(a\)

大于的情况同理。

最后更新此节点。

合并操作传两个参数

\(a\)\(b\) :要合并的两棵树的根节点

这里又用到了随机则平衡的思想,通过对每个点赋以随机权值,使整棵树的随机权值满足堆的性质,保证树较为平衡。

具体来说,若\(a\)的随机权值小于\(b\)的随机权值,就把\(b\)\(a\)的右子树合并,作为新的\(a\)的右子树。

求排名为Rank的数

左子树的大小大于等于\(Rank\)--->在左边找排名为\(Rank\)的数

左子树的大小+1刚好等于\(Rank\)--->就是他了

其他情况--->在右子树上找排名为\(Rank-t[t[now].l].size-1\)的数

其他所有操作都是分裂合并的灵活利用

代码详解

加数

split(rt,x,a,b);
rt=merge(merge(a,New(x)),b);

分出来,建新点,和回去

删数

split(rt,x,a,c),split(a,x-1,a,b);
rt=merge(merge(a,merge(t[b].l,t[b].r)),c);

分三棵树出来

\(a\) :小于\(x\)的数组成的树

\(b\) :等于\(x\)的数构成的树

\(c\) :大于\(x\)的数组成的树

\(b\)的左右儿子合并,就是删掉了\(b\)

最后记得合并回来

前驱/后继

\(x || x-1\)为关键值分成两棵树,取左边的最大或右边的最小

一定记得分完之后和回来

模板Code

Link

#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;}t[N];
int n,rt,a,b,c,tot;
inline int read(){
	int w=0;
	bool f=0;
	char ch=getchar();
	while(ch>‘9‘||ch<‘0‘){
		if(ch==‘-‘) f=1;
		ch=getchar();	
	} 
	while(ch>=‘0‘&&ch<=‘9‘){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return f?-w:w;
}
inline void update(int p){
	t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
	t[++tot].sz=1;
	t[tot].v=val;
	t[tot].rd=rand();
	return tot;
}
inline void split(int now,int k,int &a,int &b){
	if(!now) a=b=0;
	else{
		if(t[now].v<=k){
			a=now;
			split(t[now].r,k,t[now].r,b);
		}
		else{
			b=now;
			split(t[now].l,k,a,t[now].l);
		}
		update(now);
	}
	return;
}
inline int merge(int a,int b){
	if(!a||!b) return a+b;
	if(t[a].rd<t[b].rd){
		t[a].r=merge(t[a].r,b);
		update(a);
		return a;
	}
	else{
		t[b].l=merge(a,t[b].l);
		update(b);
		return b;
	}
	return 0;
}
inline int kth(int now,int rank){
	if(t[t[now].l].sz>=rank) return kth(t[now].l,rank);
	if(t[t[now].l].sz+1==rank) return now;
	return kth(t[now].r,rank-t[t[now].l].sz-1);
}
int main(){
	n=read();
	while(n--){
		int opt=read(),x=read();
		if(opt==1){
			split(rt,x,a,b);
			rt=merge(merge(a,New(x)),b);
		}
		if(opt==2){
			split(rt,x,a,c),split(a,x-1,a,b);
			rt=merge(merge(a,merge(t[b].l,t[b].r)),c);
		}
		if(opt==3){
			split(rt,x-1,a,b);
			printf("%d\n",t[a].sz+1);
			rt=merge(a,b);
		}
		if(opt==4) printf("%d\n",t[kth(rt,x)].v);
		if(opt==5){
			split(rt,x-1,a,b);
			printf("%d\n",t[kth(a,t[a].sz)].v);
			rt=merge(a,b);
		}
		if(opt==6){
			split(rt,x,a,b);
			printf("%d\n",t[kth(b,1)].v);
			rt=merge(a,b);
		}
	}
	return 0;
}

另一种应用:区间操作

Link

也是模板

区间翻转

其是就是交换左右子树,打标记,标记下传,中序遍历输出。

怎么比线段树还好打

Code

#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;bool f;}t[N];
int n,m,rt,x,y,z,tot;
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>‘9‘||ch<‘0‘) ch=getchar();
	while(ch>=‘0‘&&ch<=‘9‘){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline void spread(int p){
	if(t[p].f){
		swap(t[p].l,t[p].r);
		t[t[p].l].f^=1,t[t[p].r].f^=1;
		t[p].f=0;
	}
	return;
}
inline void update(int p){
	t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
	t[++tot].sz=1;
	t[tot].v=val;
	t[tot].rd=rand();
	return tot;
}
inline void split(int p,int k,int &a,int &b){
	if(!p) a=b=0;
	else{
		spread(p);
		if(t[t[p].l].sz<k){
			a=p;
			split(t[p].r,k-t[t[p].l].sz-1,t[p].r,b);
		}
		else{
			b=p;
			split(t[p].l,k,a,t[p].l);
		}
		update(p);
	}
	return;
}
inline int merge(int a,int b){
	if(!a||!b) return a+b;
	if(t[a].rd<t[b].rd){
		spread(b);
		t[b].l=merge(a,t[b].l);
		update(b);
		return b;
	}
	else{
		spread(a);
		t[a].r=merge(t[a].r,b);
		update(a);
		return a;
	}
	return 0;
}
inline void print(int p){
	spread(p);
	if(t[p].l) print(t[p].l);
	printf("%d ",t[p].v);
	if(t[p].r) print(t[p].r);
	return;
}
int main(){
	n=read(),m=read();
	rt=New(1);
	for(int i=2;i<=n;i++) rt=merge(rt,New(i));
	for(int i=1;i<=m;i++){
		int l=read(),r=read();
		split(rt,l-1,x,y),split(y,r-l+1,y,z);
		t[y].f^=1;
		rt=merge(merge(x,y),z);
	}
	print(rt);
	puts("");
	return 0;
}

未完待续?

关于fhq_treap的一点总结

标签:操作   要求   a+b   turn   line   read   date   www   its   

原文地址:https://www.cnblogs.com/xxbbkk/p/14352909.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!