fhq treap模板

\(\mathbf{} \begin{Bmatrix} \frac{{\Large fhq treap模板} }{{\color{Blue}\Large Template} }\mathbf{} {No.1} \end{Bmatrix}\times{}\) NeeDna

#include<bits/stdc++.h>
#define N 200010
using namespace std;
int cnt=0,root,n;//cnt给唯一空间 
int lc[N],rc[N],val[N],siz[N],fix[N],s[N];
//lc左儿子 rc同理 val真实值 siz个数(包含子树与自己) fix随机赋值  s这个节点的数字个数 
void pushup(int o){siz[o]=siz[lc[o]]+siz[rc[o]]+s[o];}//三块之和 
int newnode(int x){
	siz[++cnt]=1;val[cnt]=x;fix[cnt]=rand()%10039;//随机赋值 保证堆性质 
	s[cnt]=1;lc[cnt]=rc[cnt]=0;return cnt;//赋初值,返回空间 
}
int rk(int o,int x){
	if(o==0) return 0;//递归到叶子节点  
	if(x==val[o]) return siz[lc[o]]+1;//该节点就是x 
	if(x<val[o]) return rk(lc[o],x);//x在左侧 向左递归 
	return rk(rc[o],x)+siz[lc[o]]+s[o];//同理 注意要加上左侧跳过的siz+父亲个数 
}
int kth(int o,int k){
	if(k>=siz[lc[o]]+1&&k<=siz[lc[o]]+s[o]) return val[o];//就是这个点(区间来限制)
	if(k<=siz[lc[o]]) return kth(lc[o],k);//递归左儿子
	return kth(rc[o],k-siz[lc[o]]-s[o]);//递归右儿子 k值减去小于它的个数! 
}
void split(int &a,int &b,int o,int k){
	if(!o){a=b=0;return;}//递归到叶子节点 
	if(k<=siz[lc[o]]){b=o;split(a,lc[b],lc[o],k);
	}//第一种情况 k节点在左侧 那么切口也在左侧 所以右侧是完整的(o给b)
	//o的左儿子继续递归,儿子值给 a和b的左儿子(b右儿子满了) 
	else{a=o;split(rc[a],b,rc[o],k-siz[lc[o]]-s[o]);
	}//第二种情况 k节点在右侧 那么切口也在右侧 所以左侧是完整的(o给a)
	//o的右儿子继续递归,儿子值给 b和a的右儿子(a左儿子满了)
	pushup(o);//更新维护的siz 
}
int merge(int a,int b){
	if(!a||!b) return a+b;//如果叶子节点,返回空结点
	int x;//合并后根节点
	if(fix[a]<fix[b]){//随机赋值比较大小 (我写的是小根堆) 
		x=a;rc[x]=merge(rc[a],b);//a的随机赋值小于b,x(a)为根 x(a)的左儿子不用动(merge顺序有要求) 
	}//类似左偏树 把可能有不满足的节点再次合并(a的右儿子和b合并)易错点:rc[x]=merge(rc[a],b);
	else{x=b;lc[x]=merge(a,lc[x]);//b的随机赋值小于a,x(b)为根 x(b)的右儿子不用动(merge顺序有要求) 
	}//类似左偏树 把可能有不满足的节点再次合并(a和b的左儿子合并)易错点:lc[x]=merge(a,lc[b]);
	pushup(x);return x;//更新维护的siz 
}
void insert(int &o,int x){//注意要&o 
	if(!o){o=newnode(x);return;}//如果是空的树 创建rt 
	int k=rk(o,x);//求出是在几号位,方便分裂与合并 
	int a,b,c;//分裂成3块 
	split(a,c,o,k);//a包含x
	split(a,b,a,k-1);//a去掉了k,b是x的地址 
	if(val[b]==x){//如果不止一个x
		s[b]++;pushup(b);o=merge(a,merge(b,c));}//合并3块 个数++ 更新维护的siz 有顺序要求 不要忘写o= 
	else{o=merge(merge(a,merge(b,newnode(x))),c);}//合并4块 新建点 有顺序要求 不要忘写o= 
}
void del(int x){
	int k=rk(root,x),a,b,c;//作用和insert函数中的相同 不解释
	split(a,b,root,k-1);//k-1个数在a中 所以b的第一个数就是x 
	split(b,c,b,1);// b就是x的地址 c是剩下的点
	if(s[b]>1){
		s[b]--;pushup(b);//减去数字 更新维护的siz 
		root=merge(a,merge(b,c));//合并3块 有顺序要求 不要忘写root=
	}else root=merge(a,c);//把b扔了 有顺序要求 不要忘写root=
}
int pred(int o,int x){//找前驱 
	if(!o) return -1e8;//跑到空叶子节点 
	if(x<=val[o]) return pred(lc[o],x);//x的前驱一定在左儿子
	int ret=pred(rc[o],x);
	if(val[o]>ret) return val[o];//如果x和ret的答案代表一个点 
	else return ret;//答案在右儿子 
}
int succ(int o,int x){//找后继 
	if(!o) return 1e8;//跑到空叶子节点 
	if(x>=val[o])return succ(rc[o],x);//大于等于根,肯定在右边 写= ! 
	int ret=succ(lc[o],x);
	if(val[o]<ret) return val[o];//如果x和ret的答案代表一个点 写= ! 
	else return ret;//答案在左儿子 
}
int main(){
	scanf("%d",&n);int x;
	while(n--){
		int op,x;
		scanf("%d%d",&op,&x);
		switch(op){
			case 1:insert(root,x);break;
			case 2:del(x);break;
			case 3:printf("%d\n",rk(root,x));break;
			case 4:printf("%d\n",kth(root,x));break;
			case 5:printf("%d\n",pred(root,x));break;
			case 6:printf("%d\n",succ(root,x));break;
		}
	}
	return 0;
}



posted @ 2025-05-30 20:36  NeeDna  阅读(16)  评论(0)    收藏  举报