fhq-treap

%%% 范浩强


实现

分裂操作 Split

给定一棵平衡树 \(K\),按照每个权值 \(val\),分裂成平衡树 \(A,B\),且 \(A\) 树所有点的权值不超过 \(val\)\(B\) 树所有点的权值大于 \(val\)

代码:

void splitt(int cur,int val,int &L,int &R)
{
	if(cur==0)
	{
		L=R=0;
		return ;
	}
	if(tree[cur].val<=val)
	{
		L=cur;
		splitt(tree[cur].rx,val,tree[cur].rx,R); //寻找下一个 <=val 的点,把它拼在右子树 
	}
	else
	{
		R=cur;
		splitt(tree[cur].lx,val,L,tree[cur].lx);
	}
	pushup(cur); // 更新信息 
	return ;
}

更新信息

非常简单,更新树的大小 \(siz\) 即可。

void pushup(int cur)
{
	tree[cur].siz=tree[tree[cur].lx].siz+tree[tree[cur].rx].siz+1;
	return ;
}

合并操作 union

将两颗平衡树 \(A,B\) 合并成一棵平衡树 \(K\),但要保证 \(A\) 树的所有点权小于 \(B\) 树的所有点权。

代码:

void unionn(int &cur,int L,int R)
{
	if(L==0||R==0)
	{
		cur=L^R;
		return ;
	}
	if(tree[L].rnk<tree[R].rnk) //符号任意 
	{
		cur=L;
		// R 要成为 L 的右儿子,但是 L 有一个右儿子,于是继续合并 
		unionn(tree[L].rx,tree[L].rx,R);
	}
	else
	{
		cur=R;
		unionn(tree[R].lx,L,tree[R].lx);
	}
	pushup(cur);
	return ;
}

新建节点和和插入节点

没什么好说,直接看代码:

int addnew(int val)
{
	tree[++tot].val=val;
	tree[tot].lx=tree[tot].rx=0;
	tree[tot].siz=1;
	tree[tot].rnk=rand();
	return tot;
}

void insert(int &k,int val)
{
	int lt=0,rt=0,nw=addnew(val);
	splitt(k,val,lt,rt); // 分裂成 <= val 的和 > val 的 
	unionn(lt,lt,nw); // 再把 nw 插进去合并 
	unionn(k,lt,rt);
	return ;
}

删除节点

此处我们默认删一个节点。

void delet(int &k,int val)
{
	int lt=0,mid=0,rt=0;
	splitt(k,val,lt,rt);
	splitt(lt,val-1,lt,mid);
	// 此时 lt 中 < val,mid 中 = val,rt 中 > val 
	unionn(mid,tree[mid].lx,tree[mid].rx); // 相当于删掉 mid 这个节点,保留左儿子和右儿子 
	unionn(lt,lt,mid); //当要删全部时,去掉这一行和上一行即可(把 mid 丢掉) 
	unionn(k,lt,rt);
	return ;
}

查询排名

超级简单:

int get_rank(int &k,int val)
{
	int a=0,b=0;
	splitt(k,val-1,a,b);
	int ans=tree[a].siz+1; // 题目要求加一 
	unionn(k,a,b);
	return ans;
}

查询数值

比其它函数(除了分裂与合并)复杂,非递归版代码:

int get_num(int k,int rk)
{
	while(tree[tree[k].lx].siz+1!=rk)
	{
		if(tree[tree[k].lx].siz>=rk)
			k=tree[k].lx;
		else
		{
			rk-=tree[tree[k].lx].siz+1;
			k=tree[k].rx;
		}
	}
	return tree[k].val;
}

递归版代码:

int get_num(int k,int rk)
{
	if(tree[tree[k].lx].siz+1==rk)
		return tree[k].val;
	if(tree[tree[k].lx].siz>=rk)
		return get_num(tree[k].lx,rk);
	return get_num(tree[k].rx,rk-(tree[tree[k].lx].siz+1));
}

前驱与后继

利用前面的函数,真的好写:

int prev(int &k,int val)
{
	int a=0,b=0;
	splitt(k,val-1,a,b);
	int ans=get_num(a,tree[a].siz); //找到最大的 
	unionn(k,a,b);
	return ans;
}

int next(int &k,int val)
{
	int a=0,b=0;
	splitt(k,val,a,b);
	int ans=get_num(b,1); //找到最小的 
	unionn(k,a,b);
	return ans;
}

总代码

结合以下,我们的 P3369 就写完了:

#include<bits/stdc++.h>
using namespace std;
#define int long long 

const int N=2e5+5;
class BST
{
public:
	int val,lx,rx,siz,rnk;
}tree[N];
int tot,n,root;

void pushup(int cur) //更新信息 
{
	tree[cur].siz=tree[tree[cur].lx].siz+tree[tree[cur].rx].siz+1;
	return ;
}

void splitt(int cur,int val,int &L,int &R) //分裂成一个权值 <= val 的和一个 > val的 
{
	if(cur==0)
	{
		L=R=0;
		return ;
	}
	if(tree[cur].val<=val)
	{
		L=cur;
		splitt(tree[cur].rx,val,tree[cur].rx,R); //寻找下一个 <=val 的点,把它拼在右子树 
	}
	else
	{
		R=cur;
		splitt(tree[cur].lx,val,L,tree[cur].lx);
	}
	pushup(cur); // 更新信息 
	return ;
}

void unionn(int &cur,int L,int R) //合并,但要保证 L 的所有权值 <= R 的所有权值 
{
	if(L==0||R==0)
	{
		cur=L^R;
		return ;
	}
	if(tree[L].rnk<tree[R].rnk) //符号任意 
	{
		cur=L;
		// R 要成为 L 的右儿子,但是 L 有一个右儿子,于是继续合并 
		unionn(tree[L].rx,tree[L].rx,R);
	}
	else
	{
		cur=R;
		unionn(tree[R].lx,L,tree[R].lx);
	}
	pushup(cur);
	return ;
}

int addnew(int val) //新建节点 
{
	tree[++tot].val=val;
	tree[tot].lx=tree[tot].rx=0;
	tree[tot].siz=1;
	tree[tot].rnk=rand();
	return tot;
}

void insert(int &k,int val) //插入节点 
{
	int lt=0,rt=0,nw=addnew(val);
	splitt(k,val,lt,rt); // 分裂成 <= val 的和 > val 的 
	unionn(lt,lt,nw); // 再把 nw 插进去合并 
	unionn(k,lt,rt);
	return ;
}

void delet(int &k,int val) //删除节点 
{
	int lt=0,mid=0,rt=0;
	splitt(k,val,lt,rt);
	splitt(lt,val-1,lt,mid);
	// 此时 lt 中 < val,mid 中 = val,rt 中 > val 
	unionn(mid,tree[mid].lx,tree[mid].rx); // 相当于删掉 mid 这个节点,保留左儿子和右儿子 
	unionn(lt,lt,mid); //当要删全部时,去掉这一行和上一行即可(把 mid 丢掉) 
	unionn(k,lt,rt);
	return ;
}

int get_rank(int &k,int val) //查询排名并 +1 
{
	int a=0,b=0;
	splitt(k,val-1,a,b);
	int ans=tree[a].siz+1; // 题目要求加一 
	unionn(k,a,b);
	return ans;
}

int get_num(int k,int rk) //查询数值 
{
	while(tree[tree[k].lx].siz+1!=rk)
	{
		if(tree[tree[k].lx].siz>=rk)
			k=tree[k].lx;
		else
		{
			rk-=tree[tree[k].lx].siz+1;
			k=tree[k].rx;
		}
	}
	return tree[k].val;
}

//int get_num(int k,int rk)
//{
//	if(tree[tree[k].lx].siz+1==rk)
//		return tree[k].val;
//	if(tree[tree[k].lx].siz>=rk)
//		return get_num(tree[k].lx,rk);
//	return get_num(tree[k].rx,rk-(tree[tree[k].lx].siz+1));
//}

int prev(int &k,int val) //前驱 
{
	int a=0,b=0;
	splitt(k,val-1,a,b);
	int ans=get_num(a,tree[a].siz); //找到最大的 
	unionn(k,a,b);
	return ans;
}

int next(int &k,int val) //后继 
{
	int a=0,b=0;
	splitt(k,val,a,b);
	int ans=get_num(b,1); //找到最小的 
	unionn(k,a,b);
	return ans;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	srand(time(0));
	cin>>n;
	while(n--)
	{
		int opt,x;
		cin>>opt>>x;
		if(opt==1)
			insert(root,x);
		else if(opt==2)
			delet(root,x);
		else if(opt==3)
			cout<<get_rank(root,x)<<"\n";
		else if(opt==4)
			cout<<get_num(root,x)<<"\n";
		else if(opt==5)
			cout<<prev(root,x)<<"\n";
		else if(opt==6)
			cout<<next(root,x)<<"\n";
		else
			return 114514; 
	}
	return 0;
}
posted @ 2025-02-26 10:29  tmp_get_zip_diff  阅读(11)  评论(0)    收藏  举报