FHQ-Treap

二叉搜索树(BST)

满足对于所有节点:

  • 其左子树内所有的节点的键值,均小于其键值。
  • 其右子树内所有的节点的键值,均大于其键值。

image

(图为一个二叉搜索树)

理论上来说,它的查找顺序是\(\log\)级别的,但是它可能被卡。

这样它的时间复杂度就炸了,所以我们就把它优化成了\(Treap\)

\(Treap\)

\(Treap\)其实就是\(BST\)再加上一个优先级,每一个节点都有一个键值和一个优先级,键值满足\(BST\)的性质,优先级满足\(Heap\)的性质(即父亲节点的优先级小于子节点的优先级)

键值与优先级能够确定唯一的 \(Treap\) 形态(笛卡尔树)

\(FHQ-Treap\)

无旋 \(Treap\),主要通过分裂和合并来维护平衡。

分裂:

将一个 \(Treap\) 分成 \(x,y\) 两个 \(Treap\)

其中:

  • \(x\) 中的值都小于等于 \(k\)
  • \(y\) 中的点都大于 \(k\)

\(k=4\) 时:

(上图数均为数值)

思路:

我们应用递归实现,比较当前树根和 \(k\) 的大小,并且相应的更新 \(x\)\(y\),最后递归。

求法:

  • 当前树的根的值和 \(k\) 比:
    • 如果 \(\le k\)
      1. 使 \(x\)(即较小树的根)等于当前子树的根
      2. 分裂右子树
    • 如果 \(>k\)
      1. 使 \(y\)(即较大树的根)等于当前子树的根
      2. 分裂左子树
  • 上传(即 \(pushup\)

\(code:\)

void pushup(int now){
	fhq[now].size=fhq[fhq[now].l].size+fhq[fhq[now].r].size;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
	}else{
		if(fhq[now].sum<=k){
			x=now;
			split(fhq[now].r,k,fhq[now].r,y);
		}else{
			y=now;
			split(fhq[now].l,k,x,fhq[now].l);
		}
		pushup(now);
	}
}

合并:

将两个 \(Treap\) 按照权和大小合并成一个 \(Treap\)

输入两个被合并树的根,返回合并后的根。

思路:

这里是以权值越大越靠近根合并的。

比较两树的根的权,因为在裂时 \(x\) 较小,\(y\) 较大,所以如果 \(x\) 的权大于 \(y\),那么合并 \(y\)\(x\) 的右儿子,反之亦然。

我们发现递归容易实现。

求法:

  • 比较两个树根的权,权大的作为权小的父节点
  • 继续合并

code:

int merge(int x,int y){
	if(!x||!y){//任何一棵树为空,返回另一棵树。
		return x+y;
	}
	if(fhq[x].key>fhq[y].key){
		fhq[x].r=merge(fhq[x].r,y);
		pushup(x);
		return x;
	}else{
		fhq[y].l=merge(x,fhq[y].l);
		pushup(y);
		return y;
	}
}

插入:

给一个要插入点的值 \(sum\),把它插入到 \(Treap\) 里。

思路:

显然按 \(sum\) 把原树裂开然后把它们仨合并即可。

求法:

  • 把原树按照 \(sum\) 裂开,裂成 \(x\)\(y\)
  • 申请新节点
  • \(sum,x,y\) 合并到一起

code:

mt19937 rd(114514)
int newnode(int sum){
	fhq[++idx].sum=sum;
	fhq[idx].key=rd();
	fhq[idx].size=1;
	return idx;
}
void insert(int sum){
	int root_x,root_y;
	split(root,sum,root_x,root_y);
	root=merge(merge(root_x,newnode(sum)),root_y);
}

删除:

给定要删除点的值 \(sum\),删去一个点。

思路:

我们发现,把一个子树的左右儿子合并就相当于把根删掉,那我们把数值等于 \(sum\) 的子树拆出来,然后合并左右儿子再合并回去即可。

求法:

  • 把原树按照 \(sum\) 裂开,裂成 \(x,z\)
  • \(x\) 按照 \(sum-1\) 裂开,裂成 \(x,y\)
  • \(y\) 的左右儿子合并
  • \(x,y,z\) 合并

code:

void delet(int sum){
	int root_x,root_y,root_z;
	split(root,sum,root_x,root_z);
	split(root_x,sum-1,root_x,root_y);
	root=merge(merge(root_x,merge(fhq[root_y].l,fhq[root_y].r)),root_z);
}

根据数值查排名:

给一个 \(sum\),求树中 \(sum\) 排在第几。

思路:

我们按照 \(sum-1\) 裂成 \(x,y\) 后记录 \(x.size+1\) 即可。

求法:

  • 把原树按 \(sum-1\) 裂成 \(x,y\)
  • 记录 \(x.size+1\)
  • \(x,y\) 合并

code:

int sum_to_rank(int sum){
	int root_x,root_y,ans;
	split(root,sum-1,root_x,root_y);
	ans=fhq[root_x].size+1;
	root=merge(root_x,root_y);
	return ans;
}

根据排名查数值:

给定一个 \(rank\),求树种第 \(rank\) 个树是几。

思路:

我们从根开始找:

  • 如果左子树大小大于等于 \(rank\),显然要求的值在左子树,就接着比左子树的子树和 \(rank\) 的大小。

  • 如果小于 \(rank\),就比右子树的子树和 \(rank-\) 左子树 \(.size-1\) 的大小即可。

求法:

使用递推解决,下文 \(size\) 代指左子树 \(size\)

  • 从根开始,重复判断左儿子的 \(size\)\(rank\) 的大小直到当前的:
    • 如果 \(size+1=rank\)
      • 直接 \(break\),找到答案
    • 如果 \(size\ge rank\)
      • 把根设为左儿子
    • 否则:
      • \(rank\) 减去 \(size+1\)
      • 把根设为右儿子

code:

int rank_to_sum(int rk){
	int now=root;
	while(now){
		if(fhq[fhq[now].l].size+1==rk){
			break;
		}else if(fhq[fhq[now].l].size>=rk){
			now=fhq[now].l;
		}else{
			rk-=fhq[fhq[now].l].size+1;
			now=fhq[now].r;
		}
	}
	return fhq[now].sum;
}

求前驱:

给定 \(k\),求小于 \(k\),且最大的数。

思路:

直接找排名比 \(k\) 排名少1的数即可。

code:

int front(int k){
	return rank_to_sum(sum_to_rank(k)-1);
}

求后继:

给定 \(k\),求大于 \(x\),且最小的数。

思路:

直接找比 \(k\) 大1的值的排行即可。

code:

int behind(int k){
	return rank_to_sum(sum_to_rank(k+1));
}

例题:

P6136 【模板】普通平衡树(数据加强版) - 洛谷

code:

//#pragma GCC optimize("O2")
#include<bits/stdc++.h>
using namespace std;
mt19937 rd(114514);
struct Node{
	int l,r;
	int sum,key;
	int size;
}fhq[100010];
int idx,root,n,m,ans;
int newnode(int sum){
	fhq[++idx].sum=sum;
	fhq[idx].key=rd();
	fhq[idx].size=1;
	return idx;
}
void pushup(int now){
	fhq[now].size=fhq[fhq[now].l].size+fhq[fhq[now].r].size+1;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
	}else{
		if(fhq[now].sum<=k){
			x=now;
			split(fhq[now].r,k,fhq[now].r,y);
		}else{
			y=now;
			split(fhq[now].l,k,x,fhq[now].l);
		}
		pushup(now);
	}
}
int merge(int x,int y){
	if(!x||!y){
		return x+y;
	}
	if(fhq[x].key>fhq[y].key){
		fhq[x].r=merge(fhq[x].r,y);
		pushup(x);
		return x;
	}else{
		fhq[y].l=merge(x,fhq[y].l);
		pushup(y);
		return y;
	}
}
void insert(int sum){
	int rt_x,rt_y;
	split(root,sum,rt_x,rt_y);
	root=merge(merge(rt_x,newnode(sum)),rt_y);
}
void delet(int sum){
	int rt_x,rt_y,rt_z;
	split(root,sum,rt_x,rt_z);
	split(rt_x,sum-1,rt_x,rt_y);
	root=merge(merge(rt_x,merge(fhq[rt_y].l,fhq[rt_y].r)),rt_z);
}
int sum_to_rank(int sum){
	int rt_x,rt_y,ans;
	split(root,sum-1,rt_x,rt_y);
	ans=fhq[rt_x].size+1;
	root=merge(rt_x,rt_y);
	return ans;
}
int rank_to_sum(int rk){
	int now=root;
	while(now){
		if(fhq[fhq[now].l].size+1==rk){
			break;
		}else if(fhq[fhq[now].l].size>=rk){
			now=fhq[now].l;
		}else{
			rk-=fhq[fhq[now].l].size+1;
			now=fhq[now].r;
		}
	}
	return fhq[now].sum;
}
int front(int k){
	return rank_to_sum(sum_to_rank(k)-1);
}
int behind(int k){
	return rank_to_sum(sum_to_rank(k+1));
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int a;
		cin>>a;
		insert(a);
	}
	int la=0;
	for(int i=1;i<=m;i++){
		int t,x;
		cin>>t>>x;
		if(t==1){
			insert(x^la);
		}
		if(t==2){
			delet(x^la);
		}
		if(t==3){
			la=sum_to_rank(x^la);
			ans^=la;
		}
		if(t==4){
			la=rank_to_sum(x^la);
			ans^=la;
		}
		if(t==5){
			la=front(x^la);
			ans^=la;
		}
		if(t==6){
			la=behind(x^la);
			ans^=la;
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2025-12-15 11:36  lbh123  阅读(2)  评论(0)    收藏  举报