关于fhq_treap的一点总结

算法原理

\(fhq - treap\)是一种好用的平衡数,以分裂合并为基本操作,代码简洁优雅,能解决包括序列操作在内的大部分问题,适合初学平衡树的\(OIer\)们(比如我)学习和掌握。

核心:分裂与合并

分裂时一般传四个参数

\(now\) :当前节点

\(k\) :以权值分裂或者以排名分裂时对两棵树的要求

$a $ 和 \(b\) :传址调用,用于记录当前点的左/右儿子会变成哪一个

流程(以权值分裂为例,小于\(k\)分到左边(\(a\)这边),大于\(k\)分到右边(\(b\)这边)):

若当前点的权值小于等于我们要求的,说明这个点的左子树上的权值都小于\(k\)

那我们就要到这个点的右子树上去分。那么这个点的右儿子将会改变,传下去。同时这个点也变成了某个点的左/右儿子,修改\(a\)

大于的情况同理。

最后更新此节点。

合并操作传两个参数

\(a\)\(b\) :要合并的两棵树的根节点

这里又用到了随机则平衡的思想,通过对每个点赋以随机权值,使整棵树的随机权值满足堆的性质,保证树较为平衡。

具体来说,若\(a\)的随机权值小于\(b\)的随机权值,就把\(b\)\(a\)的右子树合并,作为新的\(a\)的右子树。

求排名为Rank的数

左子树的大小大于等于\(Rank\)--->在左边找排名为\(Rank\)的数

左子树的大小+1刚好等于\(Rank\)--->就是他了

其他情况--->在右子树上找排名为\(Rank-t[t[now].l].size-1\)的数

其他所有操作都是分裂合并的灵活利用

代码详解

加数

split(rt,x,a,b);
rt=merge(merge(a,New(x)),b);

分出来,建新点,和回去

删数

split(rt,x,a,c),split(a,x-1,a,b);
rt=merge(merge(a,merge(t[b].l,t[b].r)),c);

分三棵树出来

\(a\) :小于\(x\)的数组成的树

\(b\) :等于\(x\)的数构成的树

\(c\) :大于\(x\)的数组成的树

\(b\)的左右儿子合并,就是删掉了\(b\)

最后记得合并回来

前驱/后继

\(x || x-1\)为关键值分成两棵树,取左边的最大或右边的最小

一定记得分完之后和回来

模板Code

Link

#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;}t[N];
int n,rt,a,b,c,tot;
inline int read(){
	int w=0;
	bool f=0;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-') f=1;
		ch=getchar();	
	} 
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return f?-w:w;
}
inline void update(int p){
	t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
	t[++tot].sz=1;
	t[tot].v=val;
	t[tot].rd=rand();
	return tot;
}
inline void split(int now,int k,int &a,int &b){
	if(!now) a=b=0;
	else{
		if(t[now].v<=k){
			a=now;
			split(t[now].r,k,t[now].r,b);
		}
		else{
			b=now;
			split(t[now].l,k,a,t[now].l);
		}
		update(now);
	}
	return;
}
inline int merge(int a,int b){
	if(!a||!b) return a+b;
	if(t[a].rd<t[b].rd){
		t[a].r=merge(t[a].r,b);
		update(a);
		return a;
	}
	else{
		t[b].l=merge(a,t[b].l);
		update(b);
		return b;
	}
	return 0;
}
inline int kth(int now,int rank){
	if(t[t[now].l].sz>=rank) return kth(t[now].l,rank);
	if(t[t[now].l].sz+1==rank) return now;
	return kth(t[now].r,rank-t[t[now].l].sz-1);
}
int main(){
	n=read();
	while(n--){
		int opt=read(),x=read();
		if(opt==1){
			split(rt,x,a,b);
			rt=merge(merge(a,New(x)),b);
		}
		if(opt==2){
			split(rt,x,a,c),split(a,x-1,a,b);
			rt=merge(merge(a,merge(t[b].l,t[b].r)),c);
		}
		if(opt==3){
			split(rt,x-1,a,b);
			printf("%d\n",t[a].sz+1);
			rt=merge(a,b);
		}
		if(opt==4) printf("%d\n",t[kth(rt,x)].v);
		if(opt==5){
			split(rt,x-1,a,b);
			printf("%d\n",t[kth(a,t[a].sz)].v);
			rt=merge(a,b);
		}
		if(opt==6){
			split(rt,x,a,b);
			printf("%d\n",t[kth(b,1)].v);
			rt=merge(a,b);
		}
	}
	return 0;
}

另一种应用:区间操作

Link

也是模板

区间翻转

其是就是交换左右子树,打标记,标记下传,中序遍历输出。

怎么比线段树还好打

Code

#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;bool f;}t[N];
int n,m,rt,x,y,z,tot;
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline void spread(int p){
	if(t[p].f){
		swap(t[p].l,t[p].r);
		t[t[p].l].f^=1,t[t[p].r].f^=1;
		t[p].f=0;
	}
	return;
}
inline void update(int p){
	t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
	t[++tot].sz=1;
	t[tot].v=val;
	t[tot].rd=rand();
	return tot;
}
inline void split(int p,int k,int &a,int &b){
	if(!p) a=b=0;
	else{
		spread(p);
		if(t[t[p].l].sz<k){
			a=p;
			split(t[p].r,k-t[t[p].l].sz-1,t[p].r,b);
		}
		else{
			b=p;
			split(t[p].l,k,a,t[p].l);
		}
		update(p);
	}
	return;
}
inline int merge(int a,int b){
	if(!a||!b) return a+b;
	if(t[a].rd<t[b].rd){
		spread(b);
		t[b].l=merge(a,t[b].l);
		update(b);
		return b;
	}
	else{
		spread(a);
		t[a].r=merge(t[a].r,b);
		update(a);
		return a;
	}
	return 0;
}
inline void print(int p){
	spread(p);
	if(t[p].l) print(t[p].l);
	printf("%d ",t[p].v);
	if(t[p].r) print(t[p].r);
	return;
}
int main(){
	n=read(),m=read();
	rt=New(1);
	for(int i=2;i<=n;i++) rt=merge(rt,New(i));
	for(int i=1;i<=m;i++){
		int l=read(),r=read();
		split(rt,l-1,x,y),split(y,r-l+1,y,z);
		t[y].f^=1;
		rt=merge(merge(x,y),z);
	}
	print(rt);
	puts("");
	return 0;
}

[HNOI2004]宠物收养场

自己的一个练习题

Link

为了练习,我用了最麻烦的方法做这道题

要多想,在那之前我只能告诉自己要多想

Code

#include<bits/stdc++.h>
#define N (80010)
#define INF (998244353)
using namespace std;
struct xbk{int l,r,sz,v,rd;}t1[N],t2[N];
int n,rt1,rt2,tot1,tot2,ans,a,b,c;
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline int New1(int val){
	t1[++tot1].sz=1;
	t1[tot1].v=val;
	t1[tot1].rd=rand();
	return tot1;
}
inline int New2(int val){
	t2[++tot2].sz=1;
	t2[tot2].v=val;
	t2[tot2].rd=rand();
	return tot2;
}
inline void update1(int p){
	t1[p].sz=t1[t1[p].l].sz+t1[t1[p].r].sz+1;
}
inline void update2(int p){
	t2[p].sz=t2[t2[p].l].sz+t2[t2[p].r].sz+1;
}
inline void split1(int p,int k,int &a,int &b){
	if(!p) a=b=0;
    else{
		if(k<t1[p].v){
			b=p;
			split1(t1[p].l,k,a,t1[p].l);
		}
		else{
			a=p;
			split1(t1[p].r,k,t1[p].r,b);
		}
		update1(p);
	}
	return;
}
inline void split2(int p,int k,int &a,int &b){
	if(!p) a=b=0;
	else{
		if(k<t2[p].v){
			b=p;
			split2(t2[p].l,k,a,t2[p].l);
		}
		else{
			a=p;
			split2(t2[p].r,k,t2[p].r,b);
		}
		update2(p);
	}
	return;
}
inline int merge1(int a,int b){
	if(!a||!b) return a+b;
	if(t1[a].rd<t1[b].rd){
		t1[a].r=merge1(t1[a].r,b);
		update1(a);
		return a;
	}
	else{
		t1[b].l=merge1(a,t1[b].l);
		update1(b);
		return b;
	}
	return 0;
}
inline int merge2(int a,int b){
	if(!a||!b) return a+b;
	if(t2[a].rd<t2[b].rd){
		t2[a].r=merge2(t2[a].r,b);
		update2(a);
		return a;
	}
	else{
		t2[b].l=merge2(a,t2[b].l);
		update2(b);
		return b;
	}
	return 0;
}
inline int kth1(int p,int rank){
	if(t1[t1[p].l].sz>=rank) return kth1(t1[p].l,rank);
	if(t1[t1[p].l].sz+1==rank) return t1[p].v;
	return kth1(t1[p].r,rank-t1[t1[p].l].sz-1);
}
inline int kth2(int p,int rank){
	if(t2[t2[p].l].sz>=rank) return kth2(t2[p].l,rank);
	if(t2[t2[p].l].sz+1==rank) return t2[p].v;
	return kth2(t2[p].r,rank-t2[t2[p].l].sz-1);
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int opt=read(),x=read();
		//	puts("Bowen");
		if(opt==1){
			if(!t2[rt2].sz){
				split1(rt1,x,a,b);
				rt1=merge1(merge1(a,New1(x)),b);
			}
			else{
				int pre,suf,mx=kth2(rt2,t2[rt2].sz),mn=kth2(rt2,1);
				if(x>mn){
					split2(rt2,x-1,a,b);
					pre=kth2(a,t2[a].sz);
					rt2=merge2(a,b);
				}
				if(x<mx){
					split2(rt2,x,a,b);
					suf=kth2(b,1);
					rt2=merge2(a,b);
				}
				if(x<mn) pre=-INF;
				if(x>mx) suf=INF;
				//cout<<pre<<" "<<suf<<endl;
				int pp=x-pre,ss=suf-x;
				if(pp<=ss){
					ans+=pp;
					x=pre;
					split2(rt2,x,a,c),split2(a,x-1,a,b);
					rt2=merge2(merge2(a,merge2(t2[b].l,t2[b].r)),c);
					/*puts("T2");
					for(int i=1;i<=tot2;i++) cout<<t2[i].v<<" ";
					puts("");*/							 
				}
				else{
					ans+=ss;
					x=suf;
					split2(rt2,x,a,c),split2(a,x-1,a,b);
					rt2=merge2(merge2(a,merge2(t2[b].l,t2[b].r)),c);
					/*puts("T2");
					for(int i=1;i<=tot2;i++) cout<<t2[i].v<<" ";
					puts("");*/
				}
			}
		}
		if(opt==0){
			if(!t1[rt1].sz){
				split2(rt2,x,a,b);
				rt2=merge2(merge2(a,New2(x)),b);
			}
			else{
				int pre,suf,mx=kth1(rt1,t1[rt1].sz),mn=kth1(rt1,1);
				if(x>mn){
					split1(rt1,x-1,a,b);
					pre=kth1(a,t1[a].sz);
					rt1=merge1(a,b);
				}
				if(x<mx){
					split1(rt1,x,a,b);
					suf=kth1(b,1);
					rt1=merge1(a,b);
				}
				if(x<mn) pre=-INF;
				if(x>mx) suf=INF;
				//cout<<pre<<" "<<suf<<endl;
				int pp=x-pre,ss=suf-x;
				if(pp<=ss){
					ans+=pp;
					x=pre;
					split1(rt1,x,a,c),split1(a,x-1,a,b);
					rt1=merge1(merge1(a,merge1(t1[b].l,t1[b].r)),c);
				}
				else{
					ans+=ss;
					x=suf;
					split1(rt1,x,a,c),split1(a,x-1,a,b);
					rt1=merge1(merge1(a,merge1(t1[b].l,t1[b].r)),c);
				}
			}
		}
		while(ans>=1000000) ans-=1000000;
	}
	printf("%d\n",ans);
	return 0;
}

未完待续❀

posted @ 2021-01-31 16:18  xxbbkk  阅读(107)  评论(0编辑  收藏  举报