平衡树

Treap

原理

Treap = Binary Search Tree(二叉搜索树) + heap(大根堆)。

因为二叉搜索树能够 \(log_N\) 查询排名、前驱、后继。所以我们尝试使用二叉搜索树。

但是因为可能存在这种情况:

又因为二叉搜索树不唯一,所以我们使用大根堆来保证二叉搜索树深度最小。若修改后存在左儿子大于右儿子,则进行 zig(右旋),若存在右儿子小于左儿子则进行 zag(左旋)。

下面是图解:

变量及意义

struct Treap
{
	int l/*左儿子*/,r/*右儿子*/;
	int key/*二叉搜索树的关键词*/,val/*大根堆的关键词*/;
	int cnt1/*有多少个相同的key*/,cnt2/*本个子数有多少个累计有多少个数*/;
}tr[N];

函数讲解部分

get_node

不多说。

get_node 函数代码

int get_node(int key)
{
	tr[++idx].key=key;//二叉搜索树关键字
	tr[idx].val=rand();//随机赋值
	tr[idx].cnt1=tr[idx].cnt2=1;//个数都是1
	return idx;//返回一下编号
}

build

先件一颗空树,传入正无穷与负无穷两个节点,限制范围。

build 函数代码

void build()
{
	get_node(-inf);//加入新节点
	get_node(inf);//加入新节点
	root=1;//根节点
	tr[1].r=2;//建边
	push_up(root);//向上维护
	return;
}

push_up

本子数的节点个数等于左子树的子数节点个数加右子树的子数节点个数加上本节点的相同的 \(key\) 的数量。

push_up 函数代码

void push_up(int p){tr[p].cnt2=tr[tr[p].l].cnt2+tr[tr[p].r].cnt2+tr[p].cnt1;}

zig

先让 \(q\) 指向 \(p\),再将 \(p\) 的左儿子赋为 \(q\) 的右儿子,\(q\) 右儿子赋值为 \(p\)\(p\) 的值赋为 \(q\)。最后把修改了的值向上维护就行了。

zig 函数代码

void zig(int &p/*取地址是因为要把更改后的更节点保存下来*/)
{
	int q=tr[p].l;//存下要旋转上来的点
	tr[p].l=tr[q].r;//保证是二叉树
	tr[q].r=p;//根节点
	p=q;//根节点的更替
	push_up(tr[p].r);//向上维护
	push_up(p);//向上维护
	return;
}

zag

同理 zig,先让 \(q\) 指向 \(p\),再将 \(p\) 的右儿子赋为 \(q\) 的左儿子,\(q\) 左儿子赋值为 \(p\)\(p\) 的值赋为 \(q\)。最后把修改了的值向上维护。

zag 函数代码

void zag(int &p)
{
	int q=tr[p].r;//存下要旋转上来的点
	tr[p].r=tr[q].l;//保证是二叉树
	tr[q].l=p;//根节点
	p=q;//根节点的更替
	push_up(tr[p].l);/向上维护
	push_up(p);/向上维护
	return;
}

insert

插入操作,如果找到了,此节点的数量加 \(1\) 就可以了;若大于则递归向左子树插入,若插入完毕后左儿子的价值大于根节点价值则进行右旋;若小于则递归向右子树插入,若插入完毕后右儿子的价值大于根节点价值则进行左旋。最后记得向上维护。

insert 函数代码

void insert(int &p,int key)
{
	if(!p) p=get_node(key);
	else if(tr[p].key==key) ++tr[p].cnt1;//找到了
	else if(tr[p].key>key)//比目前的还小
	{
		insert(tr[p].l,key);//在左子树查找
		if(tr[tr[p].l].val>tr[p].val) zig(p);//是否需要右旋
	}
	else
	{
		insert(tr[p].r,key);//在右子树查找
		if(tr[tr[p].r].val>tr[p].val) zag(p);//是否需要左旋
	}
	push_up(p);//向上维护
	return;
}

erase

删除操作,若找到了,且节点个数大于 \(1\),说明此次删除不会导致节点完全消失,所以直接个数减 \(1\) 就可以了;若找到了但是节点个数为 \(1\) 且不为叶子节点,先进行旋转,再继续递归;如果找到了且节点个数为 \(1\) 且为叶子节点直接让此节点的父节点为 \(0\) 就完成了删除;若当前节点比目标节点大,往左子树查找;若当前节点比目标节点小,往右子树查找。最后一样记得向上维护。

erase 函数代码

void erase(int &p,int key)
{
	if(!p) return;
	if(tr[p].key==key)
	{
		if(tr[p].cnt1>1) --tr[p].cnt1;//个数大于1,直接减
		else if(tr[p].l||tr[p].r)//不为叶子节点,即有左儿子或右儿子
		{
			if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
			{
				zig(p);//右旋
				erase(tr[p].r,key);//再次递归
			}
			else
			{
				zag(p);//左旋
				erase(tr[p].l,key);//再次递归
			}
		}
		else p=0;//叶子节点直接删
	}
	else if(tr[p].key>key) erase(tr[p].l,key);//在左子树
	else erase(tr[p].r,key);//在右子树
	push_up(p);//向上维护
	return;
}

get_key_by_rank

查找 key 的排名,若找到了,返回其左子树的值加 \(1\),因为此节点为其左儿子之后的一个;若此节点比查询节点大,去左子树找;否则返回现在前面已有的数加上从右边递归找出的结果。

get_key_by_rank 函数代码

int get_key_by_rank(int p,int key)
{
	if(!p) return 0;
	if(tr[p].key==key) return tr[tr[p].l].cnt2+1;//找到
	if(tr[p].key>key) return get_key_by_rank(tr[p].l,key);//左边
	return tr[tr[p].l].cnt2/*左子树节点个数*/+tr[p].cnt1/*当前节点个数*/+get_key_by_rank(tr[p].r,key)/*新找到的排名*/;//右边
}

get_rank_by_key

查找排名为 \(rank\) 的点的值,若只是左子树的节点个数就已经超过了 \(rank\),那么在左子树查找;若左子树节点个数少于 \(rank\) 但左子树个数加上本节点个数的值大于 \(rank\) 说明已经找到了,直接返回子节点值;若加起来还小于\(rank\) 从右子树搜索。

get_rank_by_key 函数代码

int get_rank_by_key(int p,int rank)
{
	if(!p) return inf;
	if(tr[tr[p].l].cnt2>=rank) return get_rank_by_key(tr[p].l,rank);//在左边
	if(tr[tr[p].l].cnt2+tr[p].cnt1>=rank) return tr[p].key;//找到了
	return get_rank_by_key(tr[p].r,rank-tr[tr[p].l].cnt2-tr[p].cnt1);//在右边
}

get_pre

查找前驱,若此节点大于等于 \(key\),说明不满足要求,往左子树找;否则说明这个数满足要求与往右子树递归的结果取最大值。

get_pre 函数代码

int get_pre(int p,int key)
{
	if(!p) return -inf;//找到头了,返回最小值,方便取最大值
	if(tr[p].key>=key) return get_pre(tr[p].l,key);//不满足要求
  	return max(tr[p].key,get_pre(tr[p].r,key));//满足要求,能否找更大的。
}

get_next

查找后继,若此节点小于等于 \(key\),说明不满足要求,往右子树找;否则说明这个数满足要求与往左子树递归的结果取最小值。

get_next 函数代码

int get_next(int p,int key)
{
	if(!p) return inf;//找到头了,返回最大值,方便取最小值
  	if(tr[p].key<=key) return get_next(tr[p].r,key);//不满足要求
	return min(tr[p].key,get_next(tr[p].l,key));//满足要求,看能不能找更小的
}

main

主函数,先建空树,再对于每次操作调用对应函数,不过需要注意,get_rank_by_key 函数传值中给的排名要加 \(1\),因为有哨兵 \(-inf\),它会占用一个排名,同理 get_key_by_rank 函数传值中查到的排名要减 \(1\) 才是答案

主函数 main 函数代码

signed main()
{
	n=read();
	build();//建树
	while(n--)
	{
		opt=read();
		x=read();
		if(opt==1) insert(root,x);//插入
		else if(opt==2) erase(root,x);//删除
		else if(opt==3) cout<<get_key_by_rank(root,x)-1<<endl;//x的排名
		else if(opt==4) cout<<get_rank_by_key(root,x+1)<<endl;//排名为x的数
		else if(opt==5) cout<<get_pre(root,x)<<endl;//前驱
		else cout<<get_next(root,x)<<endl;//后继
	}
	return 0;
}

完整代码

AC Code of Luogu P3369 【模板】普通平衡树

#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(int i=l;i<=r;i++)
#define rep2(i,l,r) for(int i=l;i>=r;i--)
const int N=1e5+10;
const int inf=1e9;
using namespace std;
int n,opt,x,root,idx;
struct Treap
{
	int l,r;
	int key,val;
	int cnt1,cnt2;
}tr[N];
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
int get_node(int key)
{
	tr[++idx].key=key;
	tr[idx].val=rand();
	tr[idx].cnt1=tr[idx].cnt2=1;
	return idx;
}
void push_up(int p){tr[p].cnt2=tr[tr[p].l].cnt2+tr[tr[p].r].cnt2+tr[p].cnt1;}
void build()
{
	get_node(-inf);
	get_node(inf);
	root=1;
	tr[1].r=2;
	push_up(root);
	return;
}
void zig(int &p)
{
	int q=tr[p].l;
	tr[p].l=tr[q].r;
	tr[q].r=p;
	p=q;
	push_up(tr[p].r);
	push_up(p);
	return;
}
void zag(int &p)
{
	int q=tr[p].r;
	tr[p].r=tr[q].l;
	tr[q].l=p;
	p=q;
	push_up(tr[p].l);
	push_up(p);
	return;
}
void insert(int &p,int key)
{
	if(!p) p=get_node(key);
	else if(tr[p].key==key) ++tr[p].cnt1;
	else if(tr[p].key>key)
	{
		insert(tr[p].l,key);
		if(tr[tr[p].l].val>tr[p].val) zig(p);
	}
	else
	{
		insert(tr[p].r,key);
		if(tr[tr[p].r].val>tr[p].val) zag(p);
	}
	push_up(p);
	return;
}
void erase(int &p,int key)
{
	if(!p) return;
	if(tr[p].key==key)
	{
		if(tr[p].cnt1>1) --tr[p].cnt1;
		else if(tr[p].l||tr[p].r)
		{
			if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
			{
				zig(p);
				erase(tr[p].r,key);
			}
			else
			{
				zag(p);
				erase(tr[p].l,key);
			}
		}
		else p=0;
	}
	else if(tr[p].key>key) erase(tr[p].l,key);
	else erase(tr[p].r,key);
	push_up(p);
	return;
}
int get_key_by_rank(int p,int key)
{
	if(!p) return 0;
	if(tr[p].key==key) return tr[tr[p].l].cnt2+1;
	if(tr[p].key>key) return get_key_by_rank(tr[p].l,key);
	return tr[tr[p].l].cnt2+tr[p].cnt1+get_key_by_rank(tr[p].r,key);
}
int get_rank_by_key(int p,int rank)
{
	if(!p) return inf;
	if(tr[tr[p].l].cnt2>=rank) return get_rank_by_key(tr[p].l,rank);
	if(tr[tr[p].l].cnt2+tr[p].cnt1>=rank) return tr[p].key;
	return get_rank_by_key(tr[p].r,rank-tr[tr[p].l].cnt2-tr[p].cnt1);
}
int get_pre(int p,int key)
{
	if(!p) return -inf;
	if(tr[p].key>=key) return get_pre(tr[p].l,key);
	return max(tr[p].key,get_pre(tr[p].r,key));
}
int get_next(int p,int key)
{
	if(!p) return inf;
	if(tr[p].key<=key) return get_next(tr[p].r,key);
	return min(tr[p].key,get_next(tr[p].l,key));
}
signed main()
{
	n=read();
	build();
	while(n--)
	{
		opt=read();
		x=read();
		if(opt==1) insert(root,x);
		else if(opt==2) erase(root,x);
		else if(opt==3) cout<<get_key_by_rank(root,x)-1<<endl;
		else if(opt==4) cout<<get_rank_by_key(root,x+1)<<endl;
		else if(opt==5) cout<<get_pre(root,x)<<endl;
		else cout<<get_next(root,x)<<endl;
	}
	return 0;
}

Splay

前言

有了 Treap 的讲解这里就不在为平衡树的原理进行过多的赘述。

因为已有 Treap 基础,所以只讲解 Splay 操作与删除操作两个难点。

正文

Splay操作

此操作是 Splay 的核心,如果一味的左旋或右旋可能会导致最长链的长度始终不变,这样就会超时,所以我们可以将旋转规则分为以下三类:

三点在同侧,先旋转 \(y\) 点,再旋转 \(x\) 点,如下图:

三点不在同侧,旋转 \(x\) 两次,如下图:

只有两点,只旋转一遍 \(x\)

删除操作

先找前驱和后继,把后继 Splay 到到前驱的儿子处,然后你会发现如果该点,则一定为后继节点的左儿子。不存在就直接 Splay 一下后继就行。存在的话看一下它是不是不止 \(1\) 个,若是则直接减 \(1\),否则删除这个节点。

完整代码

AC Code of Luogu P3369 【模板】普通平衡树

#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(int i=l;i<=r;i++)
#define rep2(i,l,r) for(int i=l;i>=r;i--)
#define debug() puts("----------")
const int N=1e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
const double pi=acos(-1);
using namespace std;
typedef complex<int> ci;
typedef complex<double> cd;
int n,op,x,root,idx=1;
struct Splay//结构体 
{
	int son[2]/*son[0]为左儿子,son[1]为右儿子*/,fa/*父节点*/;
	int val/*该点的权值*/;
	int size/*该点子树的个数*/,cnt/*此点的个数(有可能插入一样的点)*/;
}tr[N];
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
void push_up(int p){tr[p].size=tr[tr[p].son[0]].size+tr[tr[p].son[1]].size+tr[p].cnt;}//计算该点子树个数 
void rotate(int x)//旋转 
{
	int y=tr[x].fa;//父节点 
	int z=tr[y].fa;//祖父节点 
	bool son1=0,son2=0;
	if(tr[y].son[1]==x) son1|=1;//此节点是父节点的左儿子还是右儿子 
	if(tr[z].son[1]==y) son2|=1;//父节点是祖父节点的左儿子还是右儿子 
	tr[z].son[son2]=x;//将孙子节点顶替掉父节点的位置 
	tr[x].fa=z;//此节点的父节点改为原本的祖父节点 
	tr[y].son[son1]=tr[x].son[son1^1];//此节点另一个子节点换到另一边 
	tr[tr[x].son[son1^1]].fa=y;//若此节点是父节点的左儿子,将它的右儿子拿来顶替自己的位置,反之将它的左儿子拿来顶替自己的位置 
	tr[x].son[son1^1]=y;//若此节点是父节点的左儿子,父节点将此节点的右儿子拿来顶替掉自己左儿子的位置,反之 此节点的左儿子拿来顶替掉自己右儿子的位置
	tr[y].fa=x;//父节点的父节点变为此节点 
	push_up(y);//向上维护 
	push_up(x);//向上维护 
	return;
}
void splay(int x,int p)//把x旋转到p的子节点 
{
	while(tr[x].fa!=p)//知道旋转到p的子节点 
	{
		int y=tr[x].fa;
		int z=tr[y].fa;
		bool son1=0,son2=0;
		if(tr[z].son[0]==y) son1|=1;
		if(tr[y].son[0]==x) son2|=1;
		if(z!=p)//若有三个点判断如何旋转 
		{
			if(son1^son2) rotate(x);//不在同一侧 
			else rotate(y);//在同一侧 
		}
		rotate(x);//一定会旋转一便x 
	}
	if(!p) root=x;//p为更节点,x因旋转了上来,所以x为新的根节点 
	return;
}
int get_node(int x,int fa)//新开一个点 
{
	int p=idx++;//编号 
	if(fa)//如果有父节点,更新父节点 
	{
		if(x>tr[fa].val) tr[fa].son[1]=p;//右儿子 
		else tr[fa].son[0]=p;//左儿子 
	}
	tr[p]={0,0,fa,x,1,1};//新开节点 
	return p;//返回编号 
}
void insert(int x)//插入 
{
	int p=root,fa=0;
	while(p&&tr[p].val!=x)//直到搜到根节点或找到与它相同的节点 
	{
		fa=p;
		if(x>tr[p].val) p=tr[p].son[1];
		else p=tr[p].son[0];
	}
	if(p) ++tr[p].cnt;//p不为根节点,说明原本就有值为x的节点,节点自身个数加1即可 
	else p=get_node(x,fa);//搜到根了,说明需要新开一个节点 
	splay(p,0);
	return;
}
int get_key_by_rank(int x)//查找x的排名
{
	int rank=0,p=root;
	while(p)//一直查找 
	{
		if(tr[p].val==x)//找到了 
		{
			splay(p,0);
			return tr[tr[p].son[0]].size;//返回子树个数,即为其排名 
		}
		else if(tr[p].val<x)//比x小,在左子树搜且排名加上此子树节点个数,因为这一整个子树上的都会比x小 
		{
			rank+=tr[tr[p].son[0]].size+tr[p].cnt;
			p=tr[p].son[1];
		}
		else p=tr[p].son[0];//比x大,在右子树搜 
	}
	return rank;//返回排名 
}
int get_rank_by_key(int x)
{
	if(x>tr[root].size) return inf;
	int rank=1,p=root;
	while(p)
	{
		if(rank+tr[tr[p].son[0]].size<=x&&x<rank+tr[p].cnt+tr[tr[p].son[0]].size)//因为存在重复情况,所以只要在这个范围内就证明找到了 
		{
			splay(p,0);
			return tr[p].val;//返回值 
		}
		else if(x<rank+tr[tr[p].son[0]].size) p=tr[p].son[0];//在左子树 
		else
		{
			rank+=tr[tr[p].son[0]].size+tr[p].cnt;//目前这个数已超过的数的数量 
			p=tr[p].son[1];//在右子树 
		}
	}
	return inf;
}
int get_pre(int x)
{
	int fa,pre,p=root;
	while(p)
	{
		if(tr[p].val>=x) p=tr[p].son[0];//往左子树搜索 
		else
		{
			fa=p;//改变父节点 
			pre=tr[p].val;//记录此时前驱 
			p=tr[p].son[1];//在右子树搜索 
		}
	}
	splay(fa,0);
	return pre;//返回值 
}
int get_next(int x)
{
	int fa,next,p=root;
	while(p)
	{
		if(tr[p].val<=x) p=tr[p].son[1];//在右子树搜索 
		else
		{
			fa=p;//更新父节点 
			next=tr[fa].val;//记录此时后继 
			p=tr[p].son[0];//在左子树搜索 
		}
	}
	splay(fa,0);
	return next;//返回值 
}
void erase(int x)
{
	get_next(x);
	int next=root;
	get_pre(x);
	int pre=root;
	splay(next,pre);//把此节点移到它前驱的儿子的位置 
	int p=tr[next].son[0];
	if(tr[p].val!=x) return;
	if(tr[p].cnt>1)
	{
		splay(p,0);
		--tr[p].cnt;//节点个数减1 
		--tr[p].size;//子树个数减1 
	}
	else
	{
		tr[next].son[0]=0;//删掉此时后继的左儿子,即此节点 
		splay(next,0);
	}
	return;
}
signed main()
{
	insert(inf);
	insert(-inf);
	//先插入正负无穷,可以减少之后的很多判断 
	n=read();
	while(n--)//n次操作 
	{
		op=read();
		x=read();
		if(op==1) insert(x);//插入x 
		if(op==2) erase(x);//删除x 
		if(op==3) cout<<get_key_by_rank(x)<<endl;//查找x的排名
		if(op==4) cout<<get_rank_by_key(x+1)<<endl;//查找排名为x的数 
		if(op==5) cout<<get_pre(x)<<endl;//查找x的前驱 
		if(op==6) cout<<get_next(x)<<endl;//查找x的后继 
	}
	return 0;
}

FHQ-Treap

前言

看它的名字可以发现它也是种 Treap,也就是说对于一个节点同样有堆与二叉搜索树两种结构的关键字。

正文

思想

FHQ-Treap 的中心思想是通过将一颗树分裂后进行操作,因为可以通过将一棵树分裂成为多部分从而查询或修改节点信息。

定义

  1. \(l\):左儿子编号;
  2. \(r\):右儿子编号;
  3. \(key\):二叉搜索树的关键字,也就是节点的真实值;
  4. \(val\):堆的关键字,也就是随机出来的值;
  5. \(size\):子树的大小。

函数定义部分代码。

struct Tree
{
	int l,r;//左右儿子节点
	int key/*二叉搜索树关键字*/,val/*堆关键字*/;
	int size;//子树大小
}tr[N];

get_node

建立一个新的节点,左右子树编号都赋为 \(0\)\(key\) 的值为输入的值,\(val\) 的值随机出来,\(size\)\(1\),但需要记住此函数有返回值,返回当前节点编号。

get_node 函数部分代码。

int get_node(int x)
{
	tr[++idx]={0,0,x,rand(),1};
	return idx;
}

push_up

向上传递 \(size\) 的值,也就是 tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;

push_up 函数部分代码。

void push_up(int p){tr[p].size=tr[tr[p].son[0]].size+tr[tr[p].son[1]].size+tr[p].cnt;}

split_by_key

根据 \(key\) 来分裂,首先如果发现目前的节点的 \(key\) 比目标的 \(key\) 小,说明目标在目前节点的右子树中,所以向右递归,并将左边的子树的根定义为目前节点,反之亦然。
split_by_key 函数部分代码

void split_by_key(int root,int key,int &x,int &y)
{
	if(!root)//如果没有可以再分的了
	{
		x=y=0;//赋给它0
		return;
	}
	if(tr[root].key<=key)//比目前节点小,则应该在当前节点右子树
	{
		x=root;//把左边的子树部分变成当前节点
		split_by_key(tr[root].r,key,tr[root].r,y);//继续递归
	}
	else//比目前节点小,则应该在当前节点左子树
	{
		y=root;//把右边的子树部分变成当前节点
		split_by_key(tr[root].l,key,x,tr[root].l);//继续递归
	}
	push_up(root);//更新
	return;
}

split_by_size

根据 \(size\) 来分裂,大体思路与 \(split_by_key\) 一样,只是当答案在有子树时,查找的 \(size \gets size-tr[tr[root].l].size-1\)
split_by_size 函数部分代码

void split_by_size(int root,int size,int &x,int &y)
{
	if(!root)//如果没有可以再分的了
	{
		x=y=0;//赋给它0
		return;
	}
	if(tr[tr[root].l].size+1<=size)//比目前节点小,则应该在当前节点右子树
	{
		x=root;//把左边的子树部分变成当前节点
		split_by_size(tr[root].r,size-tr[tr[root].l].size-1/*前面的都比他大,在右边要查询的size要减去左子树的大小与根节点*/,tr[root].r,y);//继续递归
	}
	else//比目前节点小,则应该在当前节点左子树
	{
		y=root;//把右边的子树部分变成当前节点
		split_by_size(tr[root].l,size,x,tr[root].l);//继续递归
	}
	push_up(root);//更新
	return;
}

merge

合并两个子树,合并是通过比较 \(val\) 的大小,如果 \(x\)\(val\)\(y\) 大,则 \(x\)\(y\) 上面,而 \(y\)\(x\) 的右子树上,反之亦然。
merge 函数部分代码

int merge(int x,int y)
{
	if(!x||!y) return x+y;//如果有一边分完了直接返回较大值
	if(tr[x].val<tr[y].val)//如果x应该在y之上
	{
		tr[x].r=merge(tr[x].r,y);//把x的右边与y继续合并
		push_up(x);//更新
		return x;//返回合并后的根节点
	}
	else//如果x 在y之下
	{
		tr[y].l=merge(x,tr[y].l);//把y的左边与x继续合并
		push_up(y);//更新
		return y;//返回合并后的根节点
	}
	return inf;

}

insert

insert 函数部分代码

void insert(int x)
{
	int l,r;
	split_by_key(root,x,l,r);//按x把树分成大于x的与小于等于x的这两种子树
	root=merge(merge(l,get_node(x)),r);//把新节点加入
	return;
}

erase

erase 函数部分代码

void erase(int x)
{
	int l,mid,r;
	split_by_key(root,x,l,r);//分成比小于等于x的与大于等于x的
	split_by_key(l,x-1,l,mid);//再分成小于x的与等于x的
	mid=merge(tr[mid].l,tr[mid].r);//把等于x的的左右子树合并,即删掉了当前根节点
	root=merge(merge(l,mid),r);//合起来
	return;
}

get_rank_by_key

get_rank_by_key 函数部分代码

int get_rank_by_key(int x)
{
	int l,r;
	split_by_key(root,x-1,l,r);//分成了小于x的与大于等于x的
	int ans=tr[l].size+1;//小于x的数量加1即x的排名
	root=merge(l,r);//合起来
	return ans;
}

get_key_by_rank

get_key_by_rank 函数部分代码

int get_key_by_rank(int x)
{
	int l,mid,r;
	split_by_size(root,x,l,r);//分成位置小于等于x,与大于x
	split_by_size(l,x-1,l,mid);//分成位置小于,与等于x
	int ans=tr[mid].key;//答案即为位置
	root=merge(merge(l,mid),r);//合起来
	return ans;
}

get_pre

get_pre 函数部分代码

int get_pre(int x)
{
	int l,mid,r,ans=inf;
	split_by_key(root,x-1,l,r);//分为小于x的与大于等于x的
	if(!tr[l].size) ans=-inf;
	split_by_size(l,tr[l].size-1,l,mid);//在小于x的子树当中位置为1~tr[l].size-1的与tr[l].size的
	ans=min(ans,tr[mid].key);//位置的值即为答案
	root=merge(merge(l,mid),r);//合起来
	return ans;
}

get_next

get_next 函数部分代码

int get_next(int x)
{
	int l,mid,r,ans=-inf;
	split_by_key(root,x,l,r);//分为小于等于x的与大于x的
	if(!tr[r].size) ans=inf;
	split_by_size(r,1,mid,r);//在大于x的子树中位置为1的与位置为2~tr[r].size的
	ans=max(ans,tr[mid].key);//位置的值即为答案
	root=merge(merge(l,mid),r);//合起来
	return ans;
}

完整代码

AC Code of Luogu P3369 【模板】普通平衡树

/*
	Luogu name: Symbolize
	Luogu uid: 672793
*/
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(register int i=l;i<=r;++i)
#define rep2(i,l,r) for(register int i=l;i>=r;--i)
#define rep3(i,x,y,z) for(register int i=x[y];~i;i=z[i])
#define rep4(i,x) for(register auto i:x)
#define debug() puts("----------")
const int N=1e6+10;
const int inf=0x3f3f3f3f3f3f3f3f;
const double pi=acos(-1);
using namespace std;
typedef complex<double> cp;
int n,idx,root;
struct Tree
{
	int l,r;//左右儿子节点
	int key/*二叉搜索树关键字*/,val/*堆关键字*/;
	int size;//子树大小
}tr[N];
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
struct FHQ_Treap
{
	void push_up(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}
	int get_node(int x)
	{
		tr[++idx]={0,0,x,rand(),1};
		return idx;
	}
	void split_by_key(int root,int key,int &x,int &y)
	{
		if(!root)//如果没有可以再分的了
		{
			x=y=0;//赋给它0
			return;
		}
		if(tr[root].key<=key)//比目前节点小,则应该在当前节点右子树
		{
			x=root;//把左边的子树部分变成当前节点
			split_by_key(tr[root].r,key,tr[root].r,y);//继续递归
		}
		else//比目前节点小,则应该在当前节点左子树
		{
			y=root;//把右边的子树部分变成当前节点
			split_by_key(tr[root].l,key,x,tr[root].l);//继续递归
		}
		push_up(root);//更新
		return;
	}
	void split_by_size(int root,int size,int &x,int &y)
	{
		if(!root)//如果没有可以再分的了
		{
			x=y=0;//赋给它0
			return;
		}
		if(tr[tr[root].l].size+1<=size)//比目前节点小,则应该在当前节点右子树
		{
			x=root;//把左边的子树部分变成当前节点
			split_by_size(tr[root].r,size-tr[tr[root].l].size-1/*前面的都比他大,在右边要查询的size要减去左子树的大小与根节点*/,tr[root].r,y);//继续递归
		}
		else//比目前节点小,则应该在当前节点左子树
		{
			y=root;//把右边的子树部分变成当前节点
			split_by_size(tr[root].l,size,x,tr[root].l);//继续递归
		}
		push_up(root);//更新
		return;
	}
	int merge(int x,int y)
	{
		if(!x||!y) return x+y;//如果有一边分完了直接返回较大值
		if(tr[x].val<tr[y].val)//如果x应该在y之上
		{
			tr[x].r=merge(tr[x].r,y);//把x的右边与y继续合并
			push_up(x);//更新
			return x;//返回合并后的根节点
		}
		else//如果x 在y之下
		{
			tr[y].l=merge(x,tr[y].l);//把y的左边与x继续合并
			push_up(y);//更新
			return y;//返回合并后的根节点
		}
		return inf;
	}
	void insert(int x)
	{
		int l,r;
		split_by_key(root,x,l,r);//按x把树分成大于x的与小于等于x的这两种子树
		root=merge(merge(l,get_node(x)),r);//把新节点加入
		return;
	}
	void erase(int x)
	{
		int l,mid,r;
		split_by_key(root,x,l,r);//分成比小于等于x的与大于等于x的
		split_by_key(l,x-1,l,mid);//再分成小于x的与等于x的
		mid=merge(tr[mid].l,tr[mid].r);//把等于x的的左右子树合并,即删掉了当前根节点
		root=merge(merge(l,mid),r);//合起来
		return;
	}
	int get_rank_by_key(int x)
	{
		int l,r;
		split_by_key(root,x-1,l,r);//分成了小于x的与大于等于x的
		int ans=tr[l].size+1;//小于x的数量加1即x的排名
		root=merge(l,r);//合起来
		return ans;
	}
	int get_key_by_rank(int x)
	{
		int l,mid,r;
		split_by_size(root,x,l,r);//分成位置小于等于x,与大于x
		split_by_size(l,x-1,l,mid);//分成位置小于,与等于x
		int ans=tr[mid].key;//答案即为位置
		root=merge(merge(l,mid),r);//合起来
		return ans;
	}
	int get_pre(int x)
	{
		int l,mid,r,ans=inf;
		split_by_key(root,x-1,l,r);//分为小于x的与大于等于x的
		if(!tr[l].size) ans=-inf;
		split_by_size(l,tr[l].size-1,l,mid);//在小于x的子树当中位置为1~tr[l].size-1的与tr[l].size的
		ans=min(ans,tr[mid].key);//位置的值即为答案
		root=merge(merge(l,mid),r);//合起来
		return ans;
	}
	int get_next(int x)
	{
		int l,mid,r,ans=-inf;
		split_by_key(root,x,l,r);//分为小于等于x的与大于x的
		if(!tr[r].size) ans=inf;
		split_by_size(r,1,mid,r);//在大于x的子树中位置为1的与位置为2~tr[r].size的
		ans=max(ans,tr[mid].key);//位置的值即为答案
		root=merge(merge(l,mid),r);//合起来
		return ans;
	}
}fhq;

signed main()
{
//	#ifndef ONLINE_JUDGE
//		freopen(".in","r",stdin);
//		freopen(".out","w",stdout);
//	#endif
	n=read();
	while(n--)
	{
		int opt=read();
		int x=read();
		if(opt==1) fhq.insert(x);
		if(opt==2) fhq.erase(x);
		if(opt==3) cout<<fhq.get_rank_by_key(x)<<endl;
		if(opt==4) cout<<fhq.get_key_by_rank(x)<<endl;
		if(opt==5) cout<<fhq.get_pre(x)<<endl;
		if(opt==6) cout<<fhq.get_next(x)<<endl;
	}
	return 0;
}
posted @ 2023-10-26 18:23  Symbolize  阅读(48)  评论(0)    收藏  举报