FHQ Treap 笔记 & 代码

前言

首先先说好,我不太懂 $ FHQ$ $Treap $。
同机房的大家都在写 $ Splay $ ,只几个人(比方说我)在用 $ Treap $……

然而这两个都不好调(对我来说就没有好调的),所以去学了 $ FHQ$ $Treap $

FHQ讲数据结构
这个需要\(VIP\)

那么接下来……

据我所知

变量定义

(因为我起变量名起得很臭) 先说明一下每个变量是什么意思。

\(1.\) \(son[x][k]\),\(k=0,1\);
代表了两个儿子,其中,当 \(k=0\) 时是左儿子, \(k=1\) 时是右儿子;

\(2.\) \(val[x]\) 权值;

\(3.\) \(ran[x]\)\(rand\)
写过 \(Treap\)应该知道,$ Treap $ 维持平衡性靠的是附加一个随机的权值,因为是随机的,所以每个权值取到的概率是一样的。那么高度也接近 $ logN $ ,所以是平衡的(扯远了

\(4.\) \(siz[x]\) 嗯,是大家想的那个,子树大小

\(5.\) \(SIZE\) 指总 \(size\)

以及一些容易理解的变量:\(root\) 是根 , $ opt $ 表示情况

两种基本操作

\(FHQ\) \(Treap\) 有两种操作(指最基础的),分别是 分裂合并

分裂:把一棵树分成两棵树
合并:把两棵树合成一棵树

(Ⅰ)合并(\(merge\)):

  • 合并操作将两个\(Treap\)合并成一个,合并时依然是依靠\(ran\)值来维持平衡。

  • 根据\(ran\)值大小不断递归,判断在左子树还是右子树。

\(code\)

int merge(int x,int y){
	if(!x||!y) return x+y;
	if(ran[x]<ran[y]){
		son[x][1]=merge(son[x][1],y);
		Up(x);return x;
	}
	else {
		son[y][0]=merge(x,son[y][0]);
		Up(y);return y;
	}
}

很惊讶吧,我没压行!

( Ⅱ )分裂(\(split\)

  • 分裂有两种,一种是按照 \(val\) 分裂,一种是按照 \(siz\) 分。

  • 比方说 普通平衡树 ,是按 \(val\) 分的;
    文艺平衡树 是按照 \(siz\) 分的,具体得看情况。

  • 如果是按\(val\)分,假设有一个权值 \(k\) ,让\(val\)小于等于 \(k\) 的节点在 左子树大于 \(k\) 的在 右子树 中。

\(val\)分的\(code\):

void split(int p,int k,int &x,int &y){
	if(!p) x=y=0;
	else{
		if(val[p]<=k) x=p,split(son[p][1],k,son[p][1],y);
		else y=p,split(son[p][0],k,x,son[p][0]);
		Up(p);
	}
}
  • 如果是按 \(siz\) 分:

$ code $ :

void split(int p,int k,int &x,int &y){
	if(!p) x=y=0;
	else{
		if(f[p])Down(p);//下面用siz,而不是val 
		if(siz[son[p][0]]<k) x=p,split(son[p][1],k-siz[son[p][0]]-1,son[p][1],y);
		else y=p,split(son[p][0],k,x,son[p][0]);
		Up(p);
	}
}

有一说一,只要有\(merge\)操作,就会有\(split\) 操作。

其他基本操作

  • 新建节点($ New_node $):
int New(int v){
	siz[++SIZE]=1;
	val[SIZE]=v;ran[SIZE]=rand();
	return SIZE;
}
  • $ Update $ :
void Up(int x){siz[x]=1+siz[son[x][0]]+siz[son[x][1]];}

平衡树的常见操作

Ⅰ, 插入(\(Insert\)

  • 把插入的新点看成一棵树,先把树按照新点分成两部分(\(split\)),再把新节点和一部分合并,合并之后再把两部分合并(\(merge\)

\(code\):

split(root,a,x,y);
root=merge(merge(x,New(a)),y);

Ⅱ,删除(\(Remove\))

个人习惯叫这个\(Remove\).

  • 考虑一件事,如果把一个树的左右子树合并,那么可以视为把这棵树的根节点删掉了。

  • 先把树按照 \(k\) 分成\(a\) , \(b\) 两部分,此时假设 \(k\)\(a\) 这部分的 最大值

  • 再把 \(a\) 分成两部分,\(x\)\(y\) .那么权值为\(k\)的节点一定是个根节点,用刚刚说的办法删掉它。

  • 最后把这几部分拼回来就行了。

\(code\):

split(root,a,x,z);split(x,a-1,x,y);
y=merge(son[y][0],son[y][1]);root=merge(merge(x,y),z);

Ⅲ,查询排名为\(K\)的数

  • 根据\(siz\)的大小不断向子树寻找
int kth(int p,int k){
	while(1){
		if(k<=siz[son[p][0]])p=son[p][0];
		else if(k==siz[son[p][0]]+1) return p;
		else k-=siz[son[p][0]]+1,p=son[p][1];
	}
}
int zpzs(int k){return val[kth(root,k)];}

Ⅳ,查询\(val\)\(x\)的排名

  • 先按\(x-1\)分裂树,那么其中一部分的最大值为\(a-1\),算一下它的\(siz\)就是答案。

\(code\):

split(root,a-1,x,y);
printf("%d\n",siz[x]+1);
root=merge(x,y);

Ⅴ,查\(x\)的前驱

  • 由于\(x\)的前驱小于等于 \(x-1\),所以按\(x-1\)分一下就行了。

\(code\):

split(root,a-1,x,y);
printf("%d\n",val[kth(x,siz[x])]);
root=merge(x,y);

Ⅵ ,查\(x\)的后继

  • 同查前驱。

\(code\):

split(root,a,x,y);
printf("%d\n",val[kth(y,1)]);
root=merge(x,y);

完整代码:

题目:【模板】普通平衡树

\(code\):

//重构次数:2 
//题目:FHQ Treap 
//不压行 
#include<bits/stdc++.h> 
using namespace std;
const int N=1e5+105;
int son[N][3],val[N],ran[N],siz[N],SIZE,n,root,x,y,z,opt,a;
void Up(int x){siz[x]=1+siz[son[x][0]]+siz[son[x][1]];} 
int New(int v){
	siz[++SIZE]=1;
	val[SIZE]=v;ran[SIZE]=rand();
	return SIZE;
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(ran[x]<ran[y]){
		son[x][1]=merge(son[x][1],y);
		Up(x);return x;
	}
	else {
		son[y][0]=merge(x,son[y][0]);
		Up(y);return y;
	}
}
void split(int p,int k,int &x,int &y){
	if(!p) x=y=0;
	else{
		if(val[p]<=k) x=p,split(son[p][1],k,son[p][1],y);
		else y=p,split(son[p][0],k,x,son[p][0]);
		Up(p);
	}
}
int kth(int p,int k){
	while(1){
		if(k<=siz[son[p][0]])p=son[p][0];
		else if(k==siz[son[p][0]]+1) return p;
		else k-=siz[son[p][0]]+1,p=son[p][1];
	}
}
int main(){
	srand(1e9+7);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&opt,&a);
		if(opt==1){//插入 
			split(root,a,x,y);
			root=merge(merge(x,New(a)),y);
		}
		else if(opt==2){
			split(root,a,x,z);split(x,a-1,x,y);
			y=merge(son[y][0],son[y][1]);root=merge(merge(x,y),z);
		}
		else if(opt==3){
			split(root,a-1,x,y);
			printf("%d\n",siz[x]+1);
			root=merge(x,y);
		}
		else if(opt==4){printf("%d\n",val[kth(root,a)]);}
		else if(opt==5){
			split(root,a-1,x,y);
			printf("%d\n",val[kth(x,siz[x])]);
			root=merge(x,y);
		}
		else if(opt==6){
			split(root,a,x,y);
			printf("%d\n",val[kth(y,1)]);
			root=merge(x,y);
		}
	}
	return 0;
}

题目:【模板】文艺平衡树

\(code\):

//重构次数:1
//题目:文艺平衡树 
//不压行 
#include<bits/stdc++.h> 
using namespace std;
const int N=1e5+105;
int son[N][3],val[N],ran[N],siz[N],SIZE,n,root,m;
bool f[N];
void Up(int x){siz[x]=1+siz[son[x][0]]+siz[son[x][1]];} 
void Down(int x){
	swap(son[x][0],son[x][1]);
	if(son[x][0])f[son[x][0]]^=1;
	if(son[x][1])f[son[x][1]]^=1;
	f[x]=0;
}
int New(int v){
	siz[++SIZE]=1;
	val[SIZE]=v;ran[SIZE]=rand();
	return SIZE;
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(ran[x]<ran[y]){
		if(f[x])Down(x);
		son[x][1]=merge(son[x][1],y);
		Up(x);return x;
	}
	else {
		if(f[y])Down(y);
		son[y][0]=merge(x,son[y][0]);
		Up(y);return y;
	}
}
void split(int p,int k,int &x,int &y){
	if(!p) x=y=0;
	else{
		if(f[p])Down(p);//下面用siz,而不是val 
		if(siz[son[p][0]]<k) x=p,split(son[p][1],k-siz[son[p][0]]-1,son[p][1],y);
		else y=p,split(son[p][0],k,x,son[p][0]);
		Up(p);
	}
}
/*int kth(int p,int k){
	while(1){
		if(k<=siz[son[p][0]])p=son[p][0];
		else if(k==siz[son[p][0]]+1) return p;
		else k-=siz[son[p][0]]+1,p=son[p][1];
	}
}*/
void out(int x){
	if(!x) return ;
	if(f[x])Down(x);
	out(son[x][0]);
	printf("%d ",val[x]) ;
	out(son[x][1]);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)root=merge(root,New(i));
	for(int i=1;i<=m;i++){
		int l,r,x,y,z;
		scanf("%d%d",&l,&r);
		split(root,l-1,x,y);split(y,r-l+1,y,z);
		f[y]^=1;root=merge(x,merge(y,z));
	}
	out(root);
	return 0;
}

书写ing

posted @ 2021-08-12 21:03  Nickle-NI  阅读(55)  评论(0编辑  收藏  举报