1. 程式人生 > 實用技巧 >fhq-treap,splay 模板

fhq-treap,splay 模板

這兩個一般都可以用來處理區間問題
實測 fhq-treap 比 splay 常數更大一些

普通平衡樹:https://www.luogu.com.cn/problem/P3369

fhq-treap,這裡分裂寫的是按大小分裂

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<ctime>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct tr{
	tr *ls,*rs;
	int val,rnd,size;
}*root;
inline void pushup(tr *tree){
	tree->size=1+(tree->ls?tree->ls->size:0)+(tree->rs?tree->rs->size:0);
}
void split(tr *tree,int k,tr *&ltree,tr *&rtree){
	if(tree==NULL) return ltree=rtree=NULL,void();
	tr *tmp;
	int ls_size=tree->ls?tree->ls->size:0;
	if(ls_size>=k){
		split(tree->ls,k,ltree,tmp);
		rtree=tree;rtree->ls=tmp;
		pushup(rtree);
	}
	else{
		split(tree->rs,k-ls_size-1,tmp,rtree);
		ltree=tree;ltree->rs=tmp;
		pushup(ltree);
	}
}
tr* merge(tr *ltree,tr *rtree){
	if(!ltree||!rtree) return ltree?ltree:rtree;
	if(ltree->rnd<rtree->rnd){
		ltree->rs=merge(ltree->rs,rtree);
		pushup(ltree);
		return ltree;
	}
	else{
		rtree->ls=merge(ltree,rtree->ls);
		pushup(rtree);
		return rtree;
	}
}
inline int rank(int val){
	reg int ans=1;
	reg tr *tree=root;
	while(tree){
		if(val<=tree->val) tree=tree->ls;
		else{
			ans+=(tree->ls?tree->ls->size:0)+1;
			tree=tree->rs;
		}
	}
	return ans;
}
inline void insert(int val){
	int rk=rank(val)-1;
	tr *x,*y;
	split(root,rk,x,y);
	tr *new_=new tr;
	new_->size=1;
	new_->val=val;new_->rnd=rand();
	new_->ls=new_->rs=NULL;
	root=merge(merge(x,new_),y);
}
inline void del(int val){
	int rk=rank(val);
	tr *x,*y,*z;
	split(root,rk,x,z);
	split(x,rk-1,x,y);//y->val=val,y->size=1
	root=merge(x,z);
	delete y;
}
inline int kth(int k){
	tr *x,*y,*z;
	split(root,k-1,x,y);
	split(y,1,y,z);
	root=merge(x,merge(y,z));
	return y->val;
}
int main(){
	srand(time(0));
	int n=read();reg int op,x;while(n--){
		op=read();x=read();
		if(op==1) insert(x);
		else if(op==2) del(x);
		else if(op==3) printf("%d\n",rank(x));
		else if(op==4) printf("%d\n",kth(x));
		else if(op==5) printf("%d\n",kth(rank(x)-1));
		else printf("%d\n",kth(rank(x+1)));
	}
	return 0;
}

splay,維護父指標的版本,https://www.bilibili.com/video/BV1wt411u7xL
放張維基上摘下來的圖:

一字型,是兩次同方向的旋轉,之字形是兩次不同方向的

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define N 100005
struct SPLAY{
	struct tr{
		tr *son[2],*fa;
		int val,cnt,size;
	}*root,*null,dizhi[N];
	int tot;
	inline int ident(tr *tree,tr *fa){return fa->son[1]==tree;}//0:tree is leftson of fa,1:right
	inline void connect(tr *tree,tr *fa,int k){//將 tree 連成 fa 的兒子,k 確定左兒子還是右兒子
		fa->son[k]=tree;tree->fa=fa;
	}
	inline void update(tr *tree){tree->size=tree->son[0]->size+tree->son[1]->size+tree->cnt;}
	inline void rotate(tr *tree){//tree 要旋轉的兒子節點
		tr *fa=tree->fa,*faa=fa->fa;int k=ident(tree,fa);
		connect(tree->son[k^1],fa,k);
		connect(tree,faa,ident(fa,faa));
		connect(fa,tree,k^1);
		update(fa);update(tree);
	}
	inline void splay(tr *x,tr *top){//x 轉到 top 的兒子(具體是左或有根據 x 的位置)
		reg tr*fa,*faa;
		if(top==null) root=x;
		//改 root 指標,如果不寫這句,只是把 x 轉到了 root 的位置,但 root 這個指標還並沒有指向 x
		while(x->fa!=top){
			fa=x->fa;faa=fa->fa;
			if(faa!=top) ident(fa,faa)^ident(x,fa)?rotate(x):rotate(fa);
			//相同異或得零,一字型,以 fa 為兒子轉,否則以 x 為兒子
			rotate(x);//第二次一定是以 x 為兒子
		}
//			if(root->fa!=null) puts("WTF???");
	}
	void del(tr *tree,int val){
		if(val==tree->val){
			splay(tree,null);
			if(tree->cnt>1) tree->cnt--,tree->size--;
			else if(tree->son[1]==null) root=root->son[0],root->fa=null;//更新 root 的 fa
			else{
				tr *p=tree->son[1];
				while(p->son[0]!=null) p=p->son[0];
				splay(p,tree);
				connect(tree->son[0],p,0);
				root=p;
				root->fa=null;
				update(root);
			}
		}
		else del(tree->son[val>tree->val],val);
	} 
	void insert(tr *&tree,int val,tr *fa){
		if(tree==null){
			tree=&dizhi[++tot];
			tree->son[0]=tree->son[1]=null;
			tree->cnt=tree->size=1;
			tree->val=val;
			tree->fa=fa;
			splay(tree,null);
			return;
		}
		if(val==tree->val) tree->size++,tree->cnt++,splay(tree,null);
		else insert(tree->son[val>tree->val],val,tree);
	}
	int rank(reg int val){
		reg tr *tree=root;int ans=1;
		while(tree!=null){
			if(tree->val==val){
				ans+=tree->son[0]->size;
				splay(tree,null);return ans;
			}
			if(val<tree->val) tree=tree->son[0];
			else ans+=tree->son[0]->size+tree->cnt,tree=tree->son[1];
		}
		return ans;
	}
	int kth(reg int rank){
		reg tr *tree=root;
		while(tree!=null){
			if(rank>tree->son[0]->size&&rank<=tree->son[0]->size+tree->cnt){
				splay(tree,null);return tree->val;
			}
			if(rank<=tree->son[0]->size) tree=tree->son[0];
			else rank-=tree->son[0]->size+tree->cnt,tree=tree->son[1];
		}
	}
	inline void init(){
		root=null=&dizhi[0];
		root->fa=null;
	}
}splay;
int main(){
//		std::freopen("P3369_6.in","r",stdin);
//		std::freopen("out","w",stdout);
	int n=read();
	reg int op,x;splay.init();
	while(n--){
		op=read();x=read();
		if(op==1) splay.insert(splay.root,x,splay.null);
		else if(op==2) splay.del(splay.root,x);
		else if(op==3) printf("%d\n",splay.rank(x));
		else if(op==4) printf("%d\n",splay.kth(x));
		else if(op==5) printf("%d\n",splay.kth(splay.rank(x)-1));
		else printf("%d\n",splay.kth(splay.rank(x+1)));
	}
	return 0;
}

文藝平衡樹:https://www.luogu.com.cn/problem/P3391

splay,這裡 splay 不再是一個二叉權值查詢樹了,每個節點維護的 val 對應原序列中的一個元素,但是並不保證左兒子的 val 都比他小,右兒子的都比他大
那麼,一個節點左兒子的大小加一,就是它在原數裡中的下標
然後每次通過把 \(l-1\),轉到根節點,然後 \(r+1\) 轉到根節點的右子樹,這樣,\([l-1,r+1]\) 的所有數就都在根節點的右兒子的左兒子裡了,從這裡打懶標記,記得下傳
還得插入一個極大值和極小值

一開始想寫了個不維護父指標的,結果發現不能比較權值而是得比較左子樹兒子大小,很是麻煩,口胡失敗,所以還是寫的維護父指標

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct SPLAY{
	struct tr{
		tr *son[2],*fa;
		int val,size;
		int rev;
	}*root,*null,dizhi[100005];
	int tot;
	inline void update(tr *tree){tree->size=1+tree->son[0]->size+tree->son[1]->size;}
	inline void pushdown(tr *tree){
		if(!tree->rev||tree==null) return;
		std::swap(tree->son[0],tree->son[1]);
		tree->son[0]->rev^=1;tree->son[1]->rev^=1;
		tree->rev=0;
	}
	inline int ident(tr *tree,tr *fa){return fa->son[1]==tree;}
	inline void connect(tr *tree,tr *fa,int k){fa->son[k]=tree;tree->fa=fa;}
	inline void rotate(tr *tree){
		tr *fa=tree->fa,*faa=fa->fa;
		pushdown(fa);pushdown(tree);
		int k=ident(tree,fa);
		connect(tree->son[k^1],fa,k);
		connect(fa,tree,k^1);
		connect(tree,faa,ident(fa,faa));
		update(fa);update(tree);
	}
	void splay(tr *tree,tr *top){
		reg tr *fa,*faa;
		if(top==null) pushdown(root),root=tree;
		while(tree->fa!=top){
			fa=tree->fa;faa=fa->fa;
			if(faa!=top) ident(fa,faa)^ident(tree,fa)?rotate(tree):rotate(fa);
			rotate(tree);
		}
	}
	void insert(tr *&tree,int val,tr *fa){
		if(tree==null){
			tree=&dizhi[++tot];
			tree->val=val;tree->fa=fa;
			tree->son[0]=tree->son[1]=null;
			tree->size=1;
			splay(tree,null);
			return;
		}
		insert(tree->son[val>tree->val],val,tree);
	}
	inline tr *get_node(int rank){
		reg tr *tree=root;
		while(tree!=null){
			pushdown(tree);
			if(rank==tree->son[0]->size+1){
				splay(tree,null);return tree;
			}
			if(rank<=tree->son[0]->size) tree=tree->son[0];
			else rank-=tree->son[0]->size+1,tree=tree->son[1];
		}
	}
	inline void rev(int l,int r){
		tr *L=get_node(l-1),*R=get_node(r+1);
		//再後面兩個 splay 操作前先獲取這兩個節點
		//如果直接把函式值當作引數往後面的 splay 傳的話,第二個 get_node 裡的 splay 操作會改變樹結構
		//出現錯誤
		splay(L,null);//root->rs:[l,n]
		splay(R,root);//root->rs->ls:[l,r]
		root->son[1]->son[0]->rev^=1;
	}
	void dfs(tr *tree){
		pushdown(tree);
		if(tree->son[0]!=null) dfs(tree->son[0]);
		if(tree->val!=-1&&tree->val!=1e9) printf("%d ",tree->val);
		if(tree->son[1]!=null) dfs(tree->son[1]);
	}
}splay;
int main(){
	int n=read(),m=read();
	splay.root=splay.null=&splay.dizhi[0];
	splay.root->fa=splay.null;
	splay.insert(splay.root,-1,splay.null);
	splay.insert(splay.root,1e9,splay.null);
	for(reg int i=1;i<=n;i++)
		splay.insert(splay.root,i,splay.null);
	reg int l,r;while(m--){
		l=read()+1;r=read()+1;
		splay.rev(l,r);
	}
	splay.dfs(splay.root);
	return 0;
}

fhq-treap,就是把每次要處理的那個區間的樹分裂出來,打懶標記

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<ctime>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct tr{
	tr *ls,*rs;
	int val,rnd;
	int size,rev;
}*root;
inline void pushup(tr *tree){
	tree->size=1+(tree->ls?tree->ls->size:0)+(tree->rs?tree->rs->size:0);
}
inline void pushdown(tr *tree){
	if(!tree->rev) return;
	std::swap(tree->ls,tree->rs);
	if(tree->ls) tree->ls->rev^=1;
	if(tree->rs) tree->rs->rev^=1;
	tree->rev=0;
}
void split(tr *tree,int size,tr *&ltree,tr *&rtree){
	if(!tree) return ltree=rtree=NULL,void();
	tr *tmp;
	pushdown(tree);
	int lssize=tree->ls?tree->ls->size:0;
	if(lssize<size){
		split(tree->rs,size-lssize-1,tmp,rtree);
		ltree=tree;ltree->rs=tmp;
		pushup(ltree);
	}
	else{
		split(tree->ls,size,ltree,tmp);
		rtree=tree;rtree->ls=tmp;
		pushup(rtree);
	}
}
tr* merge(tr *ltree,tr *rtree){
	if(!ltree) return rtree;
	if(!rtree) return ltree;
	if(ltree->rnd<rtree->rnd){
		pushdown(ltree);
		ltree->rs=merge(ltree->rs,rtree);
		pushup(ltree);return ltree;
	}
	else{
		pushdown(rtree);
		rtree->ls=merge(ltree,rtree->ls);
		pushup(rtree);return rtree;
	}
}
inline void reverse(int l,int r){
	tr *x,*y,*z;
	split(root,l-1,x,y);
	split(y,r-l+1,y,z);
	y->rev^=1;
	root=merge(merge(x,y),z);
}
void dfs(tr *tree){
	pushdown(tree);
	if(tree->ls) dfs(tree->ls);
	printf("%d ",tree->val);
	if(tree->rs) dfs(tree->rs);
}
int main(){
	srand(time(0));
	int n=read(),m=read();
	for(reg int i=1;i<=n;i++){
		tr *new_=new tr;
		new_->size=1;
		new_->val=i;new_->rnd=rand();
		new_->ls=new_->rs=NULL;
		root=merge(root,new_);
	}
	reg int l,r;while(m--){
		l=read();r=read();
		reverse(l,r);
	}
	dfs(root);
	return 0;
}