Fhq-Treap学习笔记

前言

\(fhq-Treap\),又称\(~\)非旋\(Treap\)
\(2011\)年中国\(IOI\)国家队选手\(fhq\)提出的一种平衡树。
在定义和结构上与\(treap\)类似,但是改变了\(treap\)利用旋转维护平衡的方式。
\(nb\)的平衡树,几乎可以支持所有其他平衡树的操作。
而最让我感到惊讶的是,\(fhq-Treap\)所有功能只基于分裂(\(split\))和合并(\(merge\))两个操作。
(\(fhq-Treap\)还可以持久化。
易理解,码量小,但常数略大,可比屑\(Splay\)还是要快的。
所以说单纯的写平衡树还是写\(fhq-Treap\)的要好。

两个核心

先声明一些变量两个函数,和\(Treap\)的更新与建点相同。

const int N=2e5+10;
int rt,cnt;//根节点和点数
int val[N],siz[N],ls[N],rs[N],pfer[N];
//点权,子树大小,左儿子,右儿子,优先值
void updata(int p){
	if(!p)return;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
int New(int x){
        int p=++cnt;
        val[p]=x;siz[p]=1;
	ls[p]=rs[p]=0;
	pfer[p]=rand()*rand();
	return p;
}

我们知道,\(Treap\)是通过旋转来维持堆性质和\(BST\)性质的,
\(fhq-Treap\)则通过\(merge\)来维护这两性质。(这里维护的是大根堆性质)
但先来看一下\(fhq-Treap\)\(split\)操作。
假如给定一个值\(k\),如何将值大于\(k\)的和小于等于\(k\)的节点分成两棵平衡树?
分别设这两棵树为\(A\)树和\(B\)树。假设我们找到一个节点\(p\),分两种情况讨论:

  1. \(val_p \leqslant k\),此时我们将它接入\(A\)树,显然\(p\)的左子树都可以接入\(A\)树,我们接续递归右子树,判断接入哪棵树。
    反之,我们同理可得:
  2. \(val_p > k\),此时我们将它接入\(B\)树,显然\(p\)的右子树都可以接入\(B\)树,我们接续递归左子树,判断接入哪棵树。
code:
void split(int p,int k,int &x,int &y){//x,y是目前到的点
	if(!p){x=y=0;return;}
	if(val[p]<=k){x=p;split(rs[p],k,rs[p],y);}
	else{y=p;split(ls[p],k,x,ls[p]);}
	updata(p);//记得更新
}

再来看下\(merge\)操作。
我们按照随机的优先值维持平衡。
假设我们合并两个根节点为\(x\)\(y\)的树。
\(pfer_x\) \(>\) \(pfer_y\),那么\(x\)是爸爸,将\(x\)左子树不动,把\(y\)\(x\)的右子树合并,再接入\(x\)的右子树。
反之亦然。

code:
int merge(int x,int y){
	if(!x||!y)return x+y;//小技巧,同max(x,y),即返回不为0的点
	if(pfer[x]>pfer[y]){
		rs[x]=merge(rs[x],y);
		updata(x);
		return x; 
	}
	else{
		ls[y]=merge(x,ls[y]);
		updata(y);
		return y;
	}
}

运用

学完这两个核心操作后,\(fhq-Treap\)已经学完一半了,其他的是它们的利用。

插入值\(k\)

先代入\(k-1\)将其分裂,新建点,然后合并

void ins(int k){
	int x,y;
	split(rt,k-1,x,y);
	rt=merge(merge(x,New(k)),y);
}
删除值\(k\)

分裂成三棵子树\(A,B,C\),其中的值分别是\(<k\) , \(=k\) , \(>k\)
\(B\)树左右儿子合并,然后合并三棵树。

void del(int k){
	int x,y,z;
	split(rt,k,x,z);
	split(x,k-1,x,y);
	if(y)y=merge(ls[y],rs[y]);
	rt=merge(merge(x,y),z);
}
查询\(k\)的排名

直接按\(k-1\)分裂,返回\(x\)树大小

int rank(int k){
	int x,y,ans;
	split(rt,k-1,x,y);
	ans=siz[x]+1;
	rt=merge(x,y);
	return ans; 
}
查询第\(k\)大/小

\(treap\)一样。

int kth(int k){
	int p=rt;
	while(true){
		if(siz[ls[p]]+1==k)break;
		else if(siz[ls[p]]+1>k)p=ls[p];
		else k-=siz[ls[p]]+1,p=rs[p]; 
	}
	return val[p];
}
查询\(k\)值前驱或后继

前驱的话,依然是按\(k-1\)分裂,在\(x\)树一直往右边走,返回最后一个值。
后继相似,只是在\(y\)一直往左边走。

int pre_nxt(int k,int op){//前驱1,后继0
	int x,y,p,ans;
	split(rt,k,x,y);
	k-=op;p=op?x:y;//小技巧
	if(op)while(rs[p])p=rs[p];
	else while(ls[p])p=ls[p];
	ans=val[p];
	rt=merge(x,y);
	return ans;
}
查询值\(k\)是否存在

与删除类似

bool find(int k){
	int x,y,z;bool ans;
	split(rt,k,x,z);
	split(x,k-1,x,y);
	if(siz[y])ans=true;
	else ans=false;
	rt=merge(merge(x,y),z);
	return ans;
}


模板题:普通平衡树
只写了\(90\)行不到,一般飞速就能码完吧。
交到\(luogu\),下面这份\(304ms\)
快读快写+\(O2\)可以到\(200ms\)左右,好像还挺快的吧(?)

Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int rt,T,cnt;
int val[N],siz[N],ls[N],rs[N],pfer[N];
void updata(int p){
	if(!p)return;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
int New(int x){
	int p=++cnt;
	val[p]=x;siz[p]=1;
	ls[p]=rs[p]=0;
	pfer[p]=rand()*rand();
	return p;
}
void split(int p,int k,int &x,int &y){
	if(!p){x=y=0;return;}
	if(val[p]<=k){x=p;split(rs[p],k,rs[p],y);}
	else{y=p;split(ls[p],k,x,ls[p]);}
	updata(p);
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(pfer[x]>pfer[y]){
		rs[x]=merge(rs[x],y);
		updata(x);
		return x; 
	}
	else{
		ls[y]=merge(x,ls[y]);
		updata(y);
		return y;
	}
}
void ins(int k){
	int x,y;
	split(rt,k-1,x,y);
	rt=merge(merge(x,New(k)),y);
}
void del(int k){
	int x,y,z;
	split(rt,k,x,z);
	split(x,k-1,x,y);
	if(y)y=merge(ls[y],rs[y]);
	rt=merge(merge(x,y),z);
}
int Rank(int k){
	int x,y,ans;
	split(rt,k-1,x,y);
	ans=siz[x]+1;
	rt=merge(x,y);
	return ans; 
}
int kth(int k){
	int p=rt;
	while(true){
		if(siz[ls[p]]+1==k)break;
		else if(siz[ls[p]]+1>k)p=ls[p];
		else k-=siz[ls[p]]+1,p=rs[p]; 
	}
	return val[p];
}
int pre_nxt(int k,int op){
	int x,y,p,ans;
    k-=op;
	split(rt,k,x,y);
	p=op?x:y;
	if(op)while(rs[p])p=rs[p];
	else while(ls[p])p=ls[p];
	ans=val[p];
	rt=merge(x,y);
	return ans;
}
int main(){
	srand(time(0));
	scanf("%d",&T);	
	while(T--){
		int op,x;
		scanf("%d%d",&op,&x);
		if(op==1)ins(x);
		if(op==2)del(x);
		if(op==3)printf("%d\n",Rank(x));
		if(op==4)printf("%d\n",kth(x));
		if(op==5||op==6)printf("%d\n",pre_nxt(x,op==5?1:0));
	}
	return 0;
}

文艺平衡树
平衡树常规操作。
打旋转标记即可,类似于线段树,平衡树的中序遍历就是最后的答案。

code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,rt,cnt;
int val[N],pfer[N],ls[N],rs[N],siz[N],tag[N];
int New(int k){
	int p=++cnt;
	ls[p]=rs[p]=0;
	siz[p]=1;val[p]=k;
	pfer[p]=rand()*rand();
	return p;
}
void updata(int p){
	if(!p)return;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
void pushdown(int p){
	if(!tag[p])return;
	swap(ls[p],rs[p]);
	tag[ls[p]]^=1;
	tag[rs[p]]^=1;
	tag[p]=0;
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(pfer[x]>pfer[y]){
		pushdown(x);
		rs[x]=merge(rs[x],y);
		updata(x);
		return x;
	}
	else{
		pushdown(y);
		ls[y]=merge(x,ls[y]);
		updata(y);
		return y; 
	}
}
void SPLIT(int p,int sz,int &x,int &y){
	if(!p){x=y=0;return;}
	pushdown(p);
	if(siz[ls[p]]+1<=sz){x=p;SPLIT(rs[p],sz-siz[ls[p]]-1,rs[p],y);}
	else{y=p;SPLIT(ls[p],sz,x,ls[p]);}
	updata(p);
}
void reverse(int l,int r){
	int x,y,z;
	SPLIT(rt,l-1,x,y);
	SPLIT(y,r-l+1,y,z);
	tag[y]^=1;
	rt=merge(merge(x,y),z);
}
void write(int p){
	if(!p)return;
	pushdown(p);
	write(ls[p]);
	printf("%d ",val[p]);
	write(rs[p]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		rt=merge(rt,New(i));
	while(m--){
		int l,r;
		scanf("%d%d",&l,&r);
		reverse(l,r);
	}	
	write(rt);
	return 0;
}

郁闷的出纳员
打个\(tag\)标记,记录整体加减的工资,插入时记得\(ins(k-tag)\)
\(fhq-Treap\)做此题的删除比其他平衡树都简单。
只要代入最小工资\(split\),把较小的那课树舍弃即可。

code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int rt,cnt,T,k;
int val[N],siz[N],ls[N],rs[N],pfer[N];
void updata(int p){
	if(!p)return;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
int New(int x){
	int p=++cnt;
	val[p]=x;siz[p]=1;
	ls[p]=rs[p]=0;
	pfer[p]=rand()*rand();
	return p;
}
void split(int p,int k,int &x,int &y){
	if(!p){x=y=0;return;}
	if(val[p]<=k) {x=p;split(rs[p],k,rs[p],y);} 
	else{y=p;split(ls[p],k,x,ls[p]);}
	updata(p);
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(pfer[x]>pfer[y]){
		rs[x]=merge(rs[x],y);
		updata(x);
		return x;
	}else{
		ls[y]=merge(x,ls[y]);
		updata(y);
		return y;
	}
}
void ins(int k){
	int x,y;
	split(rt,k-1,x,y);
	rt=merge(merge(x,New(k)),y);
}
int kth(int k){
	int p=rt;
	while(true){
		if(siz[ls[p]]+1==k)break;
		else if(siz[ls[p]]+1>k)p=ls[p];
		else k-=siz[ls[p]]+1,p=rs[p];
	}
	return val[p];
}
int Ans;
void Del(int F){
	int x,y;
	split(rt,F,x,y);
	Ans+=siz[x];
	rt=y;
}
int main(){
	scanf("%d%d",&T,&k);
	int tag=0;
	while(T--){
		char op[3];int t;
		scanf("%s%d",op,&t);
		if(op[0]=='I'&&t>=k)ins(t-tag);
		else if(op[0]=='F')printf("%d\n",siz[rt]<t?-1:kth(siz[rt]-t+1)+tag);
		else if(op[0]=='A')tag+=t;
		else if(op[0]=='S')tag-=t,Del(k-tag-1);
	}
	printf("%d",Ans);
	return 0;
}
posted @ 2021-01-28 21:07  Isenthalpic  阅读(161)  评论(5编辑  收藏  举报