have a good time!😘|

LEWISAK

园龄:10个月粉丝:13关注:10

《平衡树》读后感

第一框,世界属于fhq-treap

是什么?

你说的对,但是《fhq-treap》是由范浩强自主研发的一款全新树形数据结构。数据结构发生在一个被称作「二叉搜索树」的幻想世界,在这里,被人创造的节点将被授予「随机优先级」,导引期望 \(O(\log n)\) 之力。treap 将扮演一位名为「根据优先级建最小堆,根据权值建搜索树」的神秘角色,在自由的调试中邂逅性格各异、能力独特的同伴们——「分裂、合并、插训排名、查询 k 小值、插入、删除、查询前驱\后继」,和他们一起击败难题,找回出题人的木琴——同时,逐步发掘「大常数」的真相。

怎么做?

$$新建节点与基本定义$$

每一个点有以下几个变量:

struct zxc{
	int siz;//子树大小
	int son[2];//son[0]表示左儿子、son[1]表示右儿子
	int v;//权值
	int pri;//随机赋予的优先级
}tr[100100];

除此以外,我们还需要 root 表示根(初始为0) 和 tot 表示节点数(初始为0)

treap 的复杂度保障建立于随机优先级,简单来说,当 a 点的优先级小于 b 点的优先级时,a 就是 b 的儿子,不过现在用不着。

新建一个权值为 v 的节点代码如下:

int nw(int v){
	siz(++tot)=1;
	v(tot)=v;
	pri(tot)=rand();
	return tot;
}

顺便处理一下上传 siz 的函数:

void up(int x){
	siz(x)=1+siz(ls(x))+siz(rs(x));
	return;
}

$$合并$$

合并要实现的功能非常好理解,就是把两个节点的子树用神奇强力胶粘到一起。

但是如果你要自己处理这个过程,就会遇到些麻烦,如图所示:

所以我们利用一个递归来帮助实现过程,这样只用把程序丢到一边,让它自己分讨就可以了

int merge(int x,int y){
	if(!x||!y){//若递归出界了、就直接返回另一个
		return x+y;//因为有一个是0,所以可以偷一下懒,x+y就是非零的那一个
	}
	if(pri(x)<pri(y)){//y要成为x的儿子
		rs(x)=merge(rs(x),y);//这里的rs和ls都可以,但这是一个习惯,实际上因为需要将merge和split的处理统一化、就一般(至少对我来说)把y放到x的右儿子
		up(x);//上传siz
		return x;
	}
	else{//以下同理
		ls(y)=merge(x,ls(y));
		up(y);
		return y;
	}
}

$$分裂$$

比较简单,只需要找到需要割的位置,然后割开就可以了(这好像是废话)

分为按权值割和按子树大小割。

按权值割:

void split(int now,int v,int &x,int &y){
	if(!now){
		x=y=0;//砍下去!
		return;
	}
	if(v(now)<=v){//若当前的权值小就往右走
		x=now;//这里和merge同理,实际上x或y都可以
		split(rs(now),v,rs(now),y);
	}
	else{
		y=now;
		split(ls(now),v,x,ls(now));
	}
	up(now);
	return;
}

按子树大小割:

void split(int now,int v,int &x,int &y){
	if(!now){
		x=y=0;//砍下去!
		return;
	}
	if(siz(ls(now))>=v){//若这个点和左子树的总和太大就往左走
		y=now;//这里和merge同理,实际上x或y都可以
		split(ls(now),v,x,ls(now));
	}
	else{
		x=now;
		split(rs(now),v-siz(ls(now))-1,rs(now),y);
	}
	up(now);
	return;
}

$$查询排名为k的数$$

得益于二叉搜索树的性质,此操作可以很轻松的完成(如果你仔细想想就会发现,二叉搜索树的名字也可能来源于这一优点),如图所示:

显然可以看出,当前排名小就往右走,大就往左走,顺便统计比 now 大的个数就可以了。

代码如下:

int kth(int now,int k){
	while(1){
		if(k<siz(ls(now))+1){//大了就往左
			now=ls(now);
		}
		else{
			if(k==siz(ls(now))+1){//完全一样
				return v(now);
			}
			k-=siz(ls(now))+1;//偷懒直接改变k,可以省一个变量
			now=rs(now);//小了就往右
		}
	}
}

$$插入节点$$

正常来说、在二叉搜索树中插入一个节点需要从上往下遍历一遍,但有了分裂这样方便的操作,就只需要在需要插入的地方砍一刀就可以了。

代码如下:

void insert(int v){
	int x=0,y=0;
	split(root,v,x,y);//在需要插入的地方砍一刀
	root=merge(merge(x,nw(v)),y);//新建之后把一切粘到一块
	return;
}

$$删除$$

因为可能有同样的权值,所以要稍微麻烦一点,砍两刀之后合并然后把中间那一段丢到太空中就可以了。

代码如下:

void delet(int v){
	int x=0,y=0,z=0;
	split(root,v,x,y);//在v砍一刀
	split(x,v-1,x,z);//在v-1砍一刀
	z=merge(ls(z),rs(z));//z这个点不要,通过这个方法舍弃
	root=merge(merge(x,z),y);//合并两边
	return;
}

$$查询~x~的排名$$

砍一刀把比 x 小的扔掉然后再查询子树大小就可以了

代码如下:

int rak(int v){
	int x=0,y=0,ans=0;
	split(root,v-1,x,y);//先砍一刀
	ans=siz(x)+1;//注意排名要+1
	merge(x,y);//最后记得缝回去
	return ans;
}

$$查询前驱/后继$$

前驱要在 \(v-1\) 砍一刀,然后在全部 \(> v\) 的子树内找最小即可

int nxt(int v){
	int x=0,y=0,ans=0;
	split(root,v-1,x,y);//砍一刀
	if(!x){//若不存在更小返回极小值
		return -2147483647;
	}
	ans=kth(x,siz(x));//简单粗暴的查询最小值的方法()
	merge(x,y);//还是缝回去
	return ans;
}

后继较为类似,在 \(v\) 砍一刀,然后在全部 \(< v\) 的子树中找最大即可

int lst(int v){
	int x=0,y=0,ans=0;
	split(root,v,x,y);//砍一刀
	if(!y){//若不存在更大返回极大值
		return 2147483647;
	}
	ans=kth(y,1);//简单粗暴的查询最大值的方法()
	merge(x,y);//还是缝回去
	return ans;
}

P3369ACcode

代码有些长,压缩了一下,点击查看
#include<bits/stdc++.h> 
#define int long long
using namespace std;
struct Fhq{
	int tot=0,root=0;
	struct zxc{
		int siz,son[2],v,pri; 
	}tr[2000100];
	#define ls(x) tr[x].son[0]
	#define rs(x) tr[x].son[1]
	#define siz(x) tr[x].siz
	#define pri(x) tr[x].pri
	#define v(x) tr[x].v
	void up(int x){
		siz(x)=siz(ls(x))+siz(rs(x))+1;
		return;
	}
	int nw(int v){
		v(++tot)=v;
		siz(tot)=1;
		pri(tot)=rand();
		return tot;
	}
	int kth(int now,int k){
		while(1){
			if(k<siz(ls(now))+1){
				now=ls(now);
			}
			else{
				if(siz(ls(now))+1==k){
					return v(now);
				}
				k-=siz(ls(now))+1;
				now=rs(now);
			}
		}
	}
	void split(int now,int v,int &x,int &y){
		if(!now){
			x=y=0;
			return;
		}
		if(v(now)<=v){
			x=now;
			split(rs(now),v,rs(now),y);
		}
		else{
			y=now;
			split(ls(now),v,x,ls(now));
		}
		up(now);
		return;
	}
	int merge(int x,int y){
		if(!x||!y){
			return x+y;
		}
		if(pri(x)<pri(y)){
			rs(x)=merge(rs(x),y);
			up(x);
			return x; 
		}
		else{
			ls(y)=merge(x,ls(y));
			up(y);
			return y;
		}
	}
	void insert(int v){
		int x=0,y=0;
		split(root,v,x,y);
		root=merge(merge(x,nw(v)),y);
		return;
	}
	void delet(int v){
		int x=0,y=0,z=0;
		split(root,v,x,y);
		split(x,v-1,x,z);
		z=merge(ls(z),rs(z));
		root=merge(merge(x,z),y);
		return;
	}
	int rak(int v){
		int x=0,y=0,ans=0;
		split(root,v-1,x,y);
		ans=siz(x)+1;
		merge(x,y);
		return ans;
	}
	int nxt(int v){
		int x=0,y=0,ans=0;
		split(root,v-1,x,y);
		if(!x){
			return -2147483647;
		}
		ans=kth(x,siz(x));
		merge(x,y);
		return ans;
	}
	int lst(int v){
		int x=0,y=0,ans=0;
		split(root,v,x,y);
		if(!y){
			return 2147483647;
		}
		ans=kth(y,1);
		merge(x,y);
		return ans;
	}
}fhq;
signed main(){
	int T;
	cin>>T;
	while(T--){
		int opt,x;
		cin>>opt>>x;
		if(opt==1){
			fhq.insert(x);
		}
		if(opt==2){
			fhq.delet(x);
		}
		if(opt==3){
			cout<<fhq.rak(x)<<endl;
		}
		if(opt==4){
			cout<<fhq.kth(fhq.root,x)<<endl;
		}
		if(opt==5){
			cout<<fhq.nxt(x)<<endl;
		}
		if(opt==6){
			cout<<fhq.lst(x)<<endl;
		}
	}
}

为什么?

不会笛卡尔数,挂个链接走人。

本文作者:LEWISAK

本文链接:https://www.cnblogs.com/lewisak/p/18615931

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LEWISAK  阅读(22)  评论(0编辑  收藏  举报
/
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起