非旋treap (fhq treap)

非旋treap ( fhq treap )

学不懂splay,所以学了非旋treap
treap的效率其实和splay差不多

核心操作

fhq treap和treap同样有一个随机分配的rnd值,用于平衡 ( 在不改变性质的情况下,使树的形态趋于随机以保证平衡),但fhq treap不需要旋转操作来维持平衡,因为有两个神奇的操作merge(合并)和split(分裂)

1,分裂(split)

左小右大
1.按权值分裂
效果 :将以某个节点为根的子树分裂为两个新的子树 , 这两个子树的根节点分别为x,y .
其中x为根的子树中所有点的权值均小于等于 v, y为根的子树中的所有点的权值均 大于 v.

操作:
对以now节点为根的子树 :
1.now的权值小于等于v : 将now 与x连接,分裂右子树 (左儿子在此操作中接入了x)
2.now的权值大于v : 将now 与y连接,分裂左子树(右儿子在此操作中接入了y)
(split操作相当于将每个节点与新的左右儿子连接)
更新now
建议结合代码理解

void split(int now,int k,int &x,int &y){
    
    //注意观察&的用途
	if(!now) x=y=0;//结束
	else{
    
    
		if(tr[now].v<=k)
		x=now,split(tr[now].son[1],k,tr[now].son[1],y);
		//x=now相当于tr[祖先].son[1]=now ,右子树中剩下的委派给右儿子。(对祖先也是如此)
		else
		y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
		//y=now相当于tr[祖先].son[0]=now , 左子树中剩下的委派给左儿子。(对祖先也是如此)
		update(now);//更新 ( 因为这棵树已经“重构” )
	}
}

split
解决区间问题的时候要按size分
这时k为size 若now树的左子树有>=k个节点,那么就从其左子树中找k个
否则,从其右子树中找k-tree[lson].size-1个(左边贡献了足够多的点) 减去的1是根节点(有点像treap求第k大)

void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
	if(!now) x = y = 0;
	else {
		if(tr[now].lz) down(now);
		if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
			y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
		else 
			x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
		update(now);
	}
}

2.合并(merge)

递归的过程,在执行之前需要保证x中所有点的val值(要存储的值,不是随机值)都小于y的(简称为x < y),这是二叉搜索树的性质,而如何保证这个性质先不用着急,在插入操作中,这个性质自然实现了。最后要返回当前根节点

int merge(int x,int y){
	if(!x||!y) return x+y;//即返回非0的那个
	if(tr[x].rnd<tr[y].rnd){//rnd是随机因子
		tr[x].son[1]=merge(tr[x].son[1],y); //x为根
		update(x);
		return x;
	}
	else{
		tr[y].son[0]=merge(x,tr[y].son[0]);//y为根
		update(y);
		return y;
	} 
}

操作时注意实时维护root具体是哪个点

基本操作

1.建新节点
int new_code(int v)
{
	tot++;
	tr[tot].size=1;tr[tot].v=v;tr[tot].rnd=rand();
	return tot;
}
2.update

空节点不可update , 因为会使siz为1

int new_node(int v)
{
	tot++;
	tr[tot].size=1;tr[tot].v=v;tr[tot].rnd=rand();
	return tot;
}
3.插入

为了保证"x < y"这个性质,只好按val拆分,然后在合并x和点new的时候虽然x中有和new值相同的,但是问题不大。

split(root, val, x, y);
root = merge(merge(x, new_node(val)), y);

4.删除

注意合并的时候要反着拆分的顺序操作,仍然是为了保证"x < y"
下面的操作成功分出了一个c,而c的根的值一定为val,这是因为前面划分除出了一个a,a只有两种点,值为val的点和值<=val的点,而c又是从a中分出来的

split(root, val, a, b);
split(a, val-1, a, c);
c = merge(tree[c].lson, tree[c].rson);
root = merge(merge(a,c), b);//a,c最后拆,最先合

5.查询排名为k的数
int kth(int now,int k){
	while(1){
		if(k<=tr[tr[now].son[0]].size)//就向左
		now=tr[now].son[0];//查前驱时若前驱为空会在这里死循环,所以有时要设哨兵节点
		else{
			if(k==tr[tr[now].son[0]].size+1) return now;
			else
			{
				k-=tr[tr[now].son[0]].size+1;
				now=tr[now].son[1];//向右
			}
		}
	}
}
6.找值为val的rank

把整棵树以 val-1 split 分为x和y 然后答案就是x.size+1,当然前提是这个值存在

7.找val的前驱

按val-1划分,左树里面找最大的,即在左树里面找rank为左树size的即可

8.找val的后继

按val划分,右树rank为1的就是


P3369 【模板】普通平衡树
.

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
struct trnode{
	int son[2],v,rnd,size;
}tr[N];
int tot = 0,root=0;

void update(int x){
	tr[x].size=tr[tr[x].son[0]].size+tr[tr[x].son[1]].size+1;
}
int new_code(int v)
{
	tot++;
	tr[tot].size=1;tr[tot].v=v;tr[tot].rnd=rand();
	return tot;
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(tr[x].rnd<tr[y].rnd){
		tr[x].son[1]=merge(tr[x].son[1],y);
		update(x);
		return x;
	}
	else{
		tr[y].son[0]=merge(x,tr[y].son[0]);
		update(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now) x=y=0;
	else{
		if(tr[now].v<=k)
		x=now,split(tr[now].son[1],k,tr[now].son[1],y);
		else
		y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
		update(now);
	}
}
int kth(int now,int k){
	while(1){
		if(k<=tr[tr[now].son[0]].size)
		now=tr[now].son[0];
		else{
			if(k==tr[tr[now].son[0]].size+1) return now;
			else
			{
				k-=tr[tr[now].son[0]].size+1;
				now=tr[now].son[1];
			}
		}
	}
}
int main(){
	srand(902);
	int T;int flag ,x,y,z,a,b;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&flag,&a);
		if(flag==1){
			split(root,a,x,y);
			root=merge(merge(x,new_code(a)),y);
		}
		else if(flag == 2){
			split(root,a,x,z);
			split(x,a-1,x,y);
			y=merge(tr[y].son[0],tr[y].son[1]);
			root=merge(merge(x,y),z);
		}
		else if(flag==3){
			split(root,a-1,x,y);
			printf("%d\n",tr[x].size+1);
			root=merge(x,y);
		}
		else if(flag==4)
		printf("%d\n",tr[kth(root,a)].v);
		else if(flag==5){
			split(root,a-1,x,y);
			printf("%d\n",tr[kth(x,tr[x].size)].v);
			root=merge(x,y);
		}
		else if(flag == 6){
			split(root,a,x,y);
			printf("%d\n",tr[kth(y,1)].v);
			root=merge(x,y);
		}
	}
	return 0;
}

练习题:

P3391 【模板】文艺平衡树. -> 翻转操作
P1503 鬼子进村. -> 哨兵节点

求点赞QWQ

猜你喜欢

转载自blog.csdn.net/Josh_Shu_/article/details/109908880