FHQ-treap小结

\(FHQ\)_\(treap\)

Q:为什么学它?

A:

  • 码量小、易理解、容易调、效率还不错
  • \(LCT\)外基本都适用(\(LCT\)基本也只有\(Splay\)才能达到那个复杂度)
平衡方案:类似treap,每个节点rand一个关键字key,保证整棵树既是一个权值v的BST,又是key的heap。

基于两个基本函数\(Split\)(分裂)和\(Merge\)(合并)

函数 类型 作用
new_node(x) int 开一个权值为x的点
Merge(a,b) int 把以a为根和以b为根的树合并,返回合并后的树的根
Split(rt,&a,&b,x) void 把以root为根的树按<=x和>x分成以a和b为根的两个树
k_th(now,x) int 在now为根的树里找第x大的数
先讲讲数组意义
  • v[]:该点权值
  • w[]:该点关键字
  • si[]:该点为根的子树大小
  • t[][]:左(0)右(1)儿子
函数具体实现(请结合上表作用和代码注释理解)
  • Split

两个A(以a为根)、B(以b为根)在分一个老树(以now为根)
在还没分到之前,a、b都只是两个位置(已经分到的节点的某一个儿子),等待着有一个符合要求的子树连在这个位置

void split(int now,int &a,int &b,int k){
	if(now==0) {a=0;b=0;return;}
    //如果这个点不存在
	if(v[now]<=k) a=now,split(t[now][1],t[now][1],b,k);
    //如果这个点权值<=k,根据BST性质,左子树的一定<=k
    //那么把now及其左子树交给A(把now连在a上),然后分now的右子树
	else b=now,split(t[now][0],a,t[now][0],k);
    //如果这个点权值>k,根据BST性质,左子树的一定>k,同理即可
	si[now]=si[t[now][0]]+si[t[now][1]]+1;
    //更新这个点的si
}
  • Merge

把以a为根和以b为根的树合成一个树,并返回根
要保证a的所有数都小于b

int merge(int a,int b){
	if(!a||!b) return a+b;
    //如果只有一个子树了,返回
	if(w[a]<=w[b]){//要满足heap,所以a做根
		t[a][1]=merge(t[a][1],b);
        //a的左子树不变
        //合并a的右子树和b
		si[a]=si[t[a][0]]+si[t[a][1]]+1;
        //分了只有要更新si
		return a;
	}
	else{
		t[b][0]=merge(a,t[b][0]);
		si[b]=si[t[b][0]]+si[t[b][1]]+1;
		return b;
	}
}
  • k_th

经典BST...就算不知道看一下也应该明白了

int k_th(int now,int k){
	if(si[t[now][0]]>=k) return k_th(t[now][0],k);
	if(si[t[now][0]]+1==k) return v[now];
	return k_th(t[now][1],k-si[t[now][0]]-1);
}
操作实现
  • 插入x

    直接把原树按x分成a和b,合并a与x,再把a与x合并后的树与b合并(因为merge必须保证一个树的所有数都<=另一个树)

  • 删除x

    把原树按x-1分成a和b(>x-1),x一定在b为根的数中。然后按x把b分成b(<=x)和c,则b中的节点的权全是x。把b删掉就等于把b的左右儿子合并起来,然后再合回去就好了

  • 查x排名

    按x-1分成a(<=x-1)和b,si[a]+1就是x的排名

  • 查第x大的数

    k_th一下就好了

  • 查前驱

    按x-1分成a(<=x-1)和b,a中最大的数就是x的前驱,k_th(si[a])就好了

  • 查后继

    按x分成a和b(>x),在b中找最小的数就好了,k_th(1)就好了

注意:每次Split之后一定要Merge回去,并更新整棵树的root

#include<bits/stdc++.h>
#define ll long long
#define ri register int
using namespace std;
const int maxn=1e5+10;
int n,rt,si[maxn],v[maxn],w[maxn],t[maxn][2],cnt=0;
void split(int now,int &a,int &b,int k){
	if(now==0) {a=0;b=0;return;}
	if(v[now]<=k) a=now,split(t[now][1],t[now][1],b,k);
	else b=now,split(t[now][0],a,t[now][0],k);
	si[now]=si[t[now][0]]+si[t[now][1]]+1;
}
int merge(int a,int b){
	if(!a||!b) return a+b;
	if(w[a]<=w[b]){
		t[a][1]=merge(t[a][1],b);
		si[a]=si[t[a][0]]+si[t[a][1]]+1;
		return a;
	}
	else{
		t[b][0]=merge(a,t[b][0]);
		si[b]=si[t[b][0]]+si[t[b][1]]+1;
		return b;
	}
}
int k_th(int now,int k){
	if(si[t[now][0]]>=k) return k_th(t[now][0],k);
	if(si[t[now][0]]+1==k) return v[now];
	return k_th(t[now][1],k-si[t[now][0]]-1);
}
inline int New_Node(int x){
	si[++cnt]=1;
	v[cnt]=x;
	w[cnt]=rand();
	return cnt;
}
int main(){
	n=rd();
	rt=0;
	int a,b,c,op,x;
	for(ri i=1;i<=n;i++){
		op=rd(); x=rd();
		switch(op){
		case 1:split(rt,a,b,x);rt=merge(merge(a,New_Node(x)),b);break;
		case 2:split(rt,a,b,x-1);split(b,b,c,x);rt=merge(a,merge(merge(t[b][0],t[b][1]),c));break;
		case 3:split(rt,a,b,x-1);printf("%d\n",si[a]+1);rt=merge(a,b);break;
		case 4:printf("%d\n",k_th(rt,x));break;
		case 5:split(rt,a,b,x-1);printf("%d\n",k_th(a,si[a]));rt=merge(a,b);break;
		case 6:split(rt,a,b,x);printf("%d\n",k_th(b,1));rt=merge(a,b);break;
		}
	}
	return 0;
}

无快读五六十行\

后记

功能强大的FHQ_treap还能完成treap不能完成的区间翻转,是个很优秀的算法。

如有不对,欢迎指正
结束撒花

posted @ 2021-09-14 16:02  Lumos壹玖贰壹  阅读(37)  评论(0编辑  收藏  举报