平衡树

二叉搜索树

对于二叉搜索树上的任意一个节点 \(u\),其左子树任意一点的权值都小于它,右子树的任意一点的权值都大于它。

平衡

对于任意一节点 \(i\) 其左右节点的深度的差不超过 \(1\)

平衡树

平衡的二叉搜索树。

当一棵二叉搜索树 "大致" 平衡时,访问任意一个节点的时间复杂度是 \(O(log_n)\)的。

平衡树的实现

Treap

相对于其他的平衡树这种树的平衡性较弱,常数也比较大,因此已经没有人还在用这种做法了,构造需要用到随机数进行堆的维护,来满足平衡,增删操作就是将这一棵树进行旋转。推荐指数 \((0/10)\)

Splay

相对与 \(Treap\) 有了好的改良,关键词就是三点一线,如果出现了三个点连成了一条只线就把中间的点提起来但父节点,依旧满足二叉搜索树的性质,同时深度也减小了一层。推荐指数 \((8/10)\)

FHQ-Treap

之所以它会有这个 \(Treap\),是因为它借用了 \(Treap\) 的随机数构造的思想,同时他除了这个名字还被称为无旋平衡树,顾名思义就是不会旋转,它的主要操作分为两种:分裂(\(split\)),合并(\(merge\)),其余操作就是基于这两种操作。推荐指数\((\infty ^\infty /10)\)
可谓是非常好用。

今天讲的就是这个FHQ-Treap

节点的构造

struct no{
	int val,rnk,ls,rs,siz;//权值,构造用的随机数,左节点,右节点,子树大小
};

非常的朴素,如果随机数使用 rand() 函数需要在主函数里面添加一行srand(time(0))//创建一个随即库

新建一个节点

int add_node(int val){
	tot++;//节点的编号
	t[tot]={val,rand(),0,0,1};
	return tot;
}

重点

分裂与合并操作

//分裂
void split(int u,int &a,int &b,int val){
	if(!u){//没有节点分
		a=b=0;
		return;
	}
	if(t[u].val<=val){//按照权值进行分裂
		a=u;
		split(t[u].rs,t[u].rs,b,val);
	}else{
		b=u;
		split(t[u].ls,a,t[u].ls,val);
	}
	update(u);
	return;
}
//合并
void merge(int &u,int a,int b){//由于会有删除操作,所以需要根节点会有修改,u就表示根节点
	if(!a || !b){//只有一个节点就选你当根,也不必在往下分了
		u=a+b;
		return;
	}
	if(t[a].rnk<t[b].rnk){//按照随机值进行合并,达到平衡
		u=a;
		merge(t[a].rs,t[a].rs,b);
	}else{
		u=b;
		merge(t[b].ls,a,t[b].ls);
	}
	update(u);
	return;
}

其中的update表示对节点的子树大小修改

void update(int u){
	t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
	return;
}

一些基本操作

//插入
void insert(int &u,int val){
	int a=0,b=0,k=add_node(val);
	split(u,a,b,val);
	merge(a,a,k);
	merge(u,a,b);
	return;
}
//删除
void del(int &u,int val){
	int a=0,b=0,z=0;
	split(u,a,b,val);
	split(a,a,z,val-1);
	merge(z,t[z].ls,t[z].rs);
	merge(a,a,z);
	merge(u,a,b);
	return;
}
//寻找排名为第x的数
int find_num(int k,int x){
	while(t[t[k].ls].siz+1!=x){
		if(t[t[k].ls].siz>=x){
			k=t[k].ls;
		}else{
			x-=t[t[k].ls].siz+1; 
			k=t[k].rs;
		}
	}
	return t[k].val; 
}
//寻找大小为x的数的排名
int find_rank(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=t[a].siz+1;
	merge(k,a,b);
	return tmp;
}
//前驱
int prev(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=find_num(a,t[a].siz);
	merge(k,a,b);
	return tmp;
}
//后继
int nxtv(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val);
	int tmp=find_num(b,1);
	merge(k,a,b);
	return tmp;
}

还是比较简单的

P3369 【模板】普通平衡树

将上面的操作东拼拼西凑凑就好了

#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
struct no{
	int val,rnk,ls,rs,siz;
}t[100005];
void update(int u){
	t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
	return;
}
int add_node(int val){
	tot++;
	t[tot]={val,rand(),0,0,1};
	return tot;
}
void split(int u,int &a,int &b,int val){
	if(!u){
		a=b=0;
		return;
	}
	if(t[u].val<=val){
		a=u;
		split(t[u].rs,t[u].rs,b,val);
	}else{
		b=u;
		split(t[u].ls,a,t[u].ls,val);
	}
	update(u);
	return;
}
void merge(int &u,int a,int b){
	if(!a || !b){
		u=a+b;
		return;
	}
	if(t[a].rnk<t[b].rnk){
		u=a;
		merge(t[a].rs,t[a].rs,b);
	}else{
		u=b;
		merge(t[b].ls,a,t[b].ls);
	}
	update(u);
	return;
}
void insert(int &u,int val){
	int a=0,b=0,k=add_node(val);
	split(u,a,b,val);
	merge(a,a,k);
	merge(u,a,b);
	return;
}
void del(int &u,int val){
	int a=0,b=0,z=0;
	split(u,a,b,val);
	split(a,a,z,val-1);
	merge(z,t[z].ls,t[z].rs);
	merge(a,a,z);
	merge(u,a,b);
	return;
}
int find_num(int k,int x){
	while(t[t[k].ls].siz+1!=x){
		if(t[t[k].ls].siz>=x){
			k=t[k].ls;
		}else{
			x-=t[t[k].ls].siz+1; 
			k=t[k].rs;
		}
	}
	return t[k].val; 
}
int find_rank(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=t[a].siz+1;
	merge(k,a,b);
	return tmp;
}
int prev(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=find_num(a,t[a].siz);
	merge(k,a,b);
	return tmp;
}
int nxtv(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val);
	int tmp=find_num(b,1);
	merge(k,a,b);
	return tmp;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);	
	srand(time(0));
	int T;
	cin>>T;
	root=0;
	while(T--){
		int op,val;
		cin>>op>>val;
		if(op==1){
			insert(root,val);
		}else if(op==2){
			del(root,val);
		}else if(op==3){
			cout<<find_rank(root,val)<<"\n";
		}else if(op==4){
			cout<<find_num(root,val)<<"\n";
		}else if(op==5){
			cout<<prev(root,val)<<"\n";
		}else{
			cout<<nxtv(root,val)<<"\n";
		}
	}
	return 0;
} 

P2234 [HNOI2002] 营业额统计

找寻前驱与后继哪一个的权值与它的差更小,通过平衡树实现,需要注意安插两个哨兵,不然会越界。

#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
struct no{
	int val,rnk,ls,rs,siz;
}t[40005];
int cnt[2000005];
void update(int u){
	t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
	return;
}
int add_node(int val){
	tot++;
	t[tot]={val,rand(),0,0,1};
	return tot;
}
void split(int u,int &a,int &b,int val){
	if(!u){
		a=b=0;
		return;
	}
	if(t[u].val<=val){
		a=u;
		split(t[u].rs,t[u].rs,b,val);
	}else{
		b=u;
		split(t[u].ls,a,t[u].ls,val);
	}
	update(u);
	return;
}
void merge(int &u,int a,int b){
	if(!a || !b){
		u=a+b;
		return;
	}
	if(t[a].rnk<t[b].rnk){
		u=a;
		merge(t[a].rs,t[a].rs,b);
	}else{
		u=b;
		merge(t[b].ls,a,t[b].ls);
	}
	update(u);
	return;
}
void insert(int &u,int val){
	int a=0,b=0,k=add_node(val);
	split(u,a,b,val);
	merge(a,a,k);
	merge(u,a,b);
	return;
}
void del(int &u,int val){
	int a=0,b=0,z=0;
	split(u,a,b,val);
	split(a,a,z,val-1);
	merge(z,t[z].ls,t[z].rs);
	merge(a,a,z);
	merge(u,a,b);
	return;
}
int find_num(int k,int x){
	while(t[t[k].ls].siz+1!=x){
		if(t[t[k].ls].siz>=x){
			k=t[k].ls;
		}else{
			x-=t[t[k].ls].siz+1; 
			k=t[k].rs;
		}
	}
	return t[k].val; 
}
int find_rank(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=t[a].siz+1;
	merge(k,a,b);
	return tmp;
}
int prev(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	if(!a){
		return -1e9;
	}
	int tmp=find_num(a,t[a].siz);
	merge(k,a,b);
	return tmp;
}
int nxtv(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val);
	if(!b){
		return 1e9;
	}
	int tmp=find_num(b,1);
	merge(k,a,b);
	return tmp;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);	
	srand(time(0));
	int n;
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++){
		int a;
		cin>>a;
		if(cnt[a+1000000]){
			continue;
		}
		cnt[a+1000000]++;
		if(i==1){
			sum+=a;
			insert(root,a);
			continue;
		}
		sum+=min(a-prev(root,a),nxtv(root,a)-a);
		insert(root,a);
	}
	cout<<sum;
	return 0;
} 

P1486 [NOI2004] 郁闷的出纳员

用一个 \(tag\) 统计当前增减了多少工资,则每个人的实际工资就是初始工资加上 \(tag\),新员工不用前面的增减,所以他的初始工资应该为输入工资减去 \(tag\)

由于有员工会离家出走,所以需要在降工资的时候把初始工资 \(\le\) 最低工资 \(-tag-1\) 的员工给踢掉,他们一定时工资最小的一批,所以用分裂把他们分裂掉就好了。

#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
int n,m;
int tag;
int cnt;
struct no{
	int val,rnk,ls,rs,siz;
}t[300005];
void update(int u){
	t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
	return;
}
int add_node(int val){
	tot++;
	t[tot]={val,rand(),0,0,1};
	return tot;
}
void split(int u,int &a,int &b,int val){
	if(!u){
		a=b=0;
		return;
	}
	if(t[u].val<=val){
		a=u;
		split(t[u].rs,t[u].rs,b,val);
	}else{
		b=u;
		split(t[u].ls,a,t[u].ls,val);
	}
	update(u);
	return;
}
void merge(int &u,int a,int b){
	if(!a || !b){
		u=a+b;
		return;
	}
	if(t[a].rnk<t[b].rnk){
		u=a;
		merge(t[a].rs,t[a].rs,b);
	}else{
		u=b;
		merge(t[b].ls,a,t[b].ls);
	}
	update(u);
	return;
}
void insert(int &u,int val){
	int a=0,b=0,k=add_node(val);
	split(u,a,b,val);
	merge(a,a,k);
	merge(u,a,b);
	return;
}
void del(int &u,int val){
	int a=0,b=0,z=0;
	split(u,a,b,val);
	split(a,a,z,val-1);
	merge(z,t[z].ls,t[z].rs);
	merge(a,a,z);
	merge(u,a,b);
	return;
}
void fire(int &u){
	int a=0,b=0;
	split(u,a,b,m-tag-1);
	u=b;
	cnt+=t[a].siz;
	return;
}
int find_num(int k,int x){
	while(t[t[k].ls].siz+1!=x){
		if(t[t[k].ls].siz>=x){
			k=t[k].ls;
		}else{
			x-=t[t[k].ls].siz+1; 
			k=t[k].rs;
		}
	}
	return t[k].val; 
}
int find_rank(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=t[a].siz+1;
	merge(k,a,b);
	return tmp;
}
int prev(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val-1);
	int tmp=find_num(a,t[a].siz);
	merge(k,a,b);
	return tmp;
}
int nxtv(int &k,int val){
	int a=0,b=0;
	split(k,a,b,val);
	int tmp=find_num(b,1);
	merge(k,a,b);
	return tmp;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);	
	srand(time(0));
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		char op;
		int k;
		cin>>op>>k;
		if(op=='I'){
			if(k<m){
				continue;
			}
			insert(root,k-tag);
		}else if(op=='A'){
			tag+=k;
		}else if(op=='S'){
			tag-=k;
			fire(root);
		}else{
			if(k>t[root].siz){
				cout<<-1<<"\n";
				continue;
			}
			cout<<find_num(root,t[root].siz-k+1)+tag<<"\n";
		}
	}
	cout<<cnt;
	return 0;
} 

P2286 [HNOI2004] 宠物收养场

抓住关键同一时间呆在收养所中的,要么全是宠物,要么全是领养者,因此不用写两棵平衡树,只需记录一下当前平衡树里是宠物还是领养者,依旧寻找前驱后继,\(so\ easy\)

#include<bits/stdc++.h>
using namespace std;
struct no{
	int val,rnk,ls,rs,siz;
}t[80005];
const int mod=1000000;
int tot,root;
void update(int u){
	t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
	return;
}
int add_node(int val){
	tot++;
	t[tot]={val,rand(),0,0,1};
	return tot;
}
void split(int u,int &a,int &b,int val){
	if(!u){
		a=b=0;
		return;
	}
	if(t[u].val<=val){
		a=u;
		split(t[u].rs,t[u].rs,b,val);
	}else{
		b=u;
		split(t[u].ls,a,t[u].ls,val);
	}
	update(u);
	return;
}
void merge(int &u,int a,int b){
	if(!a || !b){
		u=a+b;
		return;
	}
	if(t[a].rnk<t[b].rnk){
		u=a;
		merge(t[a].rs,t[a].rs,b);
	}else{
		u=b;
		merge(t[b].ls,a,t[b].ls);
	}
	update(u);
	return;
}
void insert(int &u,int val){
	int a=0,b=0,c=add_node(val);
	split(u,a,b,val);
	merge(a,a,c);
	merge(u,a,b);
	return;
}
void del(int &u,int val){
	int a=0,b=0,z=0;
	split(u,a,b,val);
	split(a,a,z,val-1);
	merge(z,t[z].ls,t[z].rs);
	merge(a,a,z);
	merge(u,a,b);
	return;
}
int find_rnk(int &u,int val){
	int a=0,b=0;
	split(u,a,b,val-1);
	int tmp=t[a].siz+1;
	merge(u,a,b);
	return tmp;
}
int find_num(int u,int x){
	while(t[t[u].ls].siz+1!=x){
		if(t[t[u].ls].siz>=x){
			u=t[u].ls;
		}else{
			x-=t[t[u].ls].siz+1;
			u=t[u].rs;
		}
	}
	return t[u].val;
}
int prev(int &u,int val){
	int a=0,b=0;
	split(u,a,b,val-1);
	if(!a){
		return -1e9;
	}
	int tmp=find_num(a,t[a].siz);
	merge(u,a,b);
	return tmp;
}
int nxtv(int &u,int val){
	int a=0,b=0;
	split(u,a,b,val);
	if(!b){
		return 1e9;
	}
	int tmp=find_num(b,1);
	merge(u,a,b);
	return tmp;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	srand(time(0));
	int n;
	cin>>n;
	int f=0,sum=0;
	for(int i=1;i<=n;i++){
		int a,b;
		cin>>a>>b;
		if(!tot || t[root].siz==0){
			f=a;
			insert(root,b);
		}else if(a==f){
			insert(root,b);
		}else{
			int las=prev(root,b+1),nxt=nxtv(root,b);
			if(b-las<=nxt-b){
				sum=(sum+(b-las))%mod;
				del(root,las);
			}else{
				sum=(sum+(nxt-b))%mod;
				del(root,nxt);
			}
		}
	}
	cout<<sum;
	return 0;
}
posted @ 2026-07-24 10:40  凉茶映屏红  阅读(1)  评论(0)    收藏  举报