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