FHQ-Treap 学习笔记

範浩強之木,無旋之奇構,併合眸,妙用無窮;其當官也,避繁複之旋。其視心有二,分若離,合若聚,若星漢分合變幻,肖無跡矣。不用旋,巧避繁,古之所未有,今之所獨異。茲樹形奇,如天成,真算之妙。---------《算枢奇构》

### 基本操作

众所周知,无旋treap不需要旋转,基本操作有两个,分裂(Split)和合并(Merge)。

分离:指的是将一棵Treap按照中序遍历的顺序,分割成左右两半,满足左右两半组成的Treap的所有值都不变。

合并:指的是将两棵Treap(一般是从原先的TreapSplit出来的)合并在一起,按照中序遍历的顺序,并且所有节点的值都不变。

Split

Split操作比较简单,先讲讲如何实现:当然,Split之前要先指定一个值k,表示Split出这个Treap的中序遍历中的前k个数作为第一棵Split出的Treap。

从这个Treap的根开始,看它的左子树的大小是否大于等于k,如果是,那么说明右子树和根都在第二棵中,继续递归到左子树中。

如果不是,那么说明左子树和根都在第一棵Treap中,继续递归到右子树中,而且k要减去左子树的大小加一。

void Split(int rt,int k,int&rt1,int&rt2){
    if(!rt) {rt1=rt2=0; return;}
    if(k<=siz[ls[rt]]){
        Split(ls[rt],k,rt1,rt2);
        ls[rt]=rt2;
        combine(rt);
        rt2=rt;
    }
    else{
        Split(rs[rt],k-siz[ls[rt]]-1,rt1,rt2);
        rs[rt]=rt1;
        combine(rt);
        rt1=rt;
    }
}

Merge

考虑两个根节点的pri值,因为第一棵在第二棵前面,所以要不然rt1(第一棵的根)在rt2(第二棵的根)的左子树,要不然rt2在rt1的右子树。

但是因为有了pri的影响,所以只能rt1和rt2中pri较大的那个作为根。

如果rt1为根,那么有rt1的右子树和rt2合并作为rt1的现在的右子树。

如果rt2为根,那么有rt2的左子树和rt1合并作为rt2的现在的左子树。

两种情况都递归进子树中即可。

int Merge(int rt1,int rt2){
    if(!rt1) return rt2;
    if(!rt2) return rt1;
    if(pri[rt1]<pri[rt2]){
        rs[rt1]=Merge(rs[rt1],rt2);
        combine(rt1);
        return rt1;
    }
    else{
        ls[rt2]=Merge(rt1,ls[rt2]);
        combine(rt2);
        return rt2;
    }
}

查询排名

以当前值分裂,小的那颗树的siz就是排名

插入节点

按值分裂为两颗树,将要插入的值插在中间

删除节点

按值x和x-1拆成三棵树,将只有一个节点(也就是要删的那个)甩了,只合并另外两棵树

查询第K个值

和删除很像,分裂成三棵树,输出中间的那个节点再合并起来

区间操作

由于可以很方便的分离区间,所以结合懒标记技巧可以非常方便的操作区间,就是被忘了pushdown

模板代码

#include<bits/stdc++.h>
#define N 200005
using namespace std;

inline int read(){
	int x=0,w=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*w;
}

int t,n,m,cnt,root;
bool flag[N];
struct node{int l,r,val,key,siz;}tree[N];

void push_up(int p){
	tree[p].siz=tree[tree[p].l].siz+1+tree[tree[p].r].siz;
}
int build_new(int x){
	tree[++cnt].siz=1;
	tree[cnt].val=x;
	tree[cnt].key=rand();
	return cnt;
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(tree[x].key<tree[y].key){
		 tree[x].r=merge(tree[x].r,y);
		 push_up(x);
		 return x;
	}
	else{
		tree[y].l=merge(x,tree[y].l);
		push_up(y);
		return y;
	}
}
void split(int p,int k,int &x,int &y){
	if(!p) {
		x=y=0;
		return;
	}
	if(tree[p].val<=k) x=p,split(tree[p].r,k,tree[p].r,y);
	else y=p,split(tree[p].l,k,x,tree[p].l);
	push_up(p);
}
int kth(int p,int k){//返回的是节点编号
	if(k<=tree[tree[p].l].siz) return kth(tree[p].l,k);
	else if(k==tree[tree[p].l].siz+1) return p;
	else return kth(tree[p].r,k-tree[tree[p].l].siz-1); 
}
signed main(){
	srand((unsigned)time(NULL));
	t=read();
	while(t--){
		int opt=read(),x=read();
		if(opt==1) {
			int a,b;
			split(root,x,a,b);
			root=merge(a,merge(build_new(x),b));
		}
		else if(opt==2) {
			int a,b,c;
			split(root,x,a,b);
			split(a,x-1,a,c);
			c=merge(tree[c].l,tree[c].r);
			root=merge(merge(a,c),b);
		}
		else if(opt==3){
			int a,b;
			split(root,x-1,a,b);
			printf("%d\n",tree[a].siz+1);
			root=merge(a,b);
		}
		else if(opt==4) printf("%d\n",tree[kth(root,x)].val);
		else if(opt==5){
			int a,b;
			split(root,x-1,a,b);
			printf("%d\n",tree[kth(a,tree[a].siz)].val);
			root=merge(a,b);
		}
		else{
			int a,b;
			split(root,x,a,b);
			printf("%d\n",tree[kth(b,1)].val);
			root=merge(a,b);
		}
	}
	return 0;
}