原理

以随机数维护平衡,使树高期望为logn级别

不依靠旋转,只有两个核心操作merge(合并)和split(拆分)

因此可持久化

先介绍变量

 const int N=;
int n;
struct Node {
int val,key,siz; //权值,随机权值,子树大小
int son[]; //左右儿子(0左1右)
void res() { //清空该节点(用于删除)
son[]=son[]=siz=val=key=;
}
} tree[N];
int ins;
int mem[N],inm; //内存回收池
int root;

var

核心操作

merge并返回合并后的根

假设有两颗子树x,y,且x的所有节点的值都小于y的所有节点的值,随机权值都以小根堆的形式存储。

此时要合并x和y。我们先比较它们的根的随机权值,发现1<3,则x的左子树全部不变,让右子树继续和y合并。

这时我们发现,5>3,所以y作为rot的右儿子,y的右子树全部不变,让y的左子树继续和x合并。

由于5>4,所以y和y的右子树作为rot的左儿子,y的左子树继续和x合并。

5<7,所以接入x和它的左子树作为rot的左儿子。

发现此时x为0,所以直接返回y,合并结束。

 int merge(int x,int y) {                                                            //合并两棵树
if(!x||!y) return x+y; //若有一棵树为0则说明该树为空或已合并完成
if(tree[x].key<tree[y].key) { //若x的随机权值大于y的
tree[x].son[]=merge(tree[x].son[],y); //x的右子树和y合并,返回的根作为x的右子树
update(x);
return x; //返回x
} else {
tree[y].son[]=merge(x,tree[y].son[]); //否则y的左子树和x合并,返回的根作为y的左子树
update(y);
return y; //返回y
}
}

merge

split拆分一棵树

split有两种拆分方式,按权值拆或按排名拆。

按权值split

首先得有个基准a,即小于等于a的节点全部进入左树,大于a的节点全部进入右树。这里以a=25为例。

首先,发现rot的权值=15<25,由平衡树的性质可知,rot的左子树所有节点权值一定小于25,所以rot和它的的左子树全部进入左树,继续拆分rot的右子树。

32>25,所以rot和它的右子树全部进入右树,继续拆分rot的左子树。

29>25,同上。

24<25,所以拆分右子树。

27>25,所以拆分左子树。

发现此时rot为0,所以拆分完毕,返回。

 void split1(int now,int k,int &x,int &y) {                                            //按权值拆分两颗子树(注意要用引用)
if(!now) { //子树为0,说明无需拆分或拆分完毕,返回
x=y=;
return;
}
if(tree[now].val<=k) { //若权值小于等于k
x=now;
split1(tree[now].son[],k,tree[now].son[],y); //拆进左树并拆分右子树
} else {
y=now;
split1(tree[now].son[],k,x,tree[now].son[]); //否则拆进右树并拆分左子树
}
update(now);
}

split1

按排名split

就是把前k个节点拆入左树,其它节点拆入右树。这里以k=5为例。

rot的左子树的siz+1=3<5,所以rot和它的左子树进入左树,其他节点拆分5-3=2个节点进入左树。

4+1>2,所以rot和右子树进入右树,其它节点继续拆分出2个节点进入左树。

3+1>2,同上。

1+1=2,所以rot和左子树进入左树,其它节点继续拆分2-2=0个节点进入左树。

1+0>0,所以rot和右子树进入右树,其它节点继续拆分0个节点进入左树。

rot为0,拆分结束。

 void split2(int now,int k,int &x,int &y) {                                            //按权值拆分两颗子树(同样要用引用)
if(!now) { //子树为0,说明无需拆分或拆分完毕,返回
x=y=;
return;
}
update(now);
if(k>tree[tree[now].son[]].siz) { //若做子树大小+1小于等于k
x=now;
split2(tree[now].son[],k-tree[tree[now].son[]].siz-,tree[now].son[],y);//拆进左树并拆分右子树(注意右子树分配的名额要减少)
} else {
y=now;
split2(tree[now].son[],k,x,tree[now].son[]); //否则拆进右树并拆分左子树
}
update(now);
}

split2

其他操作

会了merge和split,其他操作就是瞎搞。

插入

插入x,先新建节点,再以x为界按权值split整棵树为a,b,再按顺序merge a,x,b。

 void insert(int x) {
int u=(inm?mem[inm--]:++ins); //新建节点
tree[u].key=rand();
tree[u].val=x;
tree[u].siz=;
int a,b;
split1(root,x,a,b); //split
root=merge(merge(a,u),b); //merge
}

insert

删除

要删除x,先将整棵树以x按权值split成a和b,再将a以x-1按权值split成c和d,则d中节点权值全为x。在d中split出排名为1的节点e和其它节点f,则e为要删的点。最后merge c,f,b。

 void delet(int x) {
int a,b,c,d,e,f;
split1(root,x,a,b); //split整棵树
split1(a,x-,c,d); //将a split为c和d
split2(d,,e,f); //将d split为e和f,则e为我们要删的节点
mem[++inm]=e; //回收
tree[e].res(); //重置
root=merge(merge(c,f),b); //merge
}

delet

查询x的排名

先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。

 int finrnk(int x) {
int a,b,c;
split1(root,x-,a,b); //split整棵树
c=tree[a].siz+; //a的大小就是小于x的数的个数
root=merge(a,b); //merge
return c;
}

finrnk

查询第x小值

先split出整棵树前x-1小节点,则右树最小节点即为所求节点,再次split即可。

 int finnum(int &rot,int x) {
int a,b,c,d,e;
split2(rot,x-,a,b); //split这棵树
split2(b,,c,d); //split出b中第1个节点
e=tree[c].val; //c即为第x小节点
rot=merge(a,merge(c,d)); //merge
return e;
}

finnum

查x前驱

将整棵树以x-1按权值split,左树中最大节点即为所求节点,转入第x小值问题。

 int las(int x) {
int a,b,c;
split1(root,x-,a,b); //split整棵树
c=finnum(a,tree[a].siz); //找左树最大值
root=merge(a,b); //merge
return c;
}

las

查x后继

将整棵树以x按权值split,右树中最小节点即为所求节点,转入第x小值问题。

 int nex(int x) {
int a,b,c;
split1(root,x,a,b); //split整棵树
c=finnum(b,); //找右树最小值
root=merge(a,b); //merge
return c;
}

nex

时空复杂度

时间复杂度

merge、split:期望树高为logn,因此复杂度为期望O(logn)

插入、删除、查询:基于以上两种操作,复杂度期望O(logn)

常数比Treap大,但比splay小的多

空间复杂度

O(n)

例题

洛谷P3369【模板】普通平衡树

 #include<bits/stdc++.h>
using namespace std;
#define INF 0x7fffffff
#define ME 0x7f
#define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
#define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
#define fel(i,a) for(register int i=h[a];i;i=ne[i])
#define ll long long
#define MEM(a,b) memset(a,b,sizeof(a))
#define maxn (100000+10)
int n;
struct Node{int val,key,siz;int son[];void res(){son[]=son[]=siz=val=key=;}}tree[maxn];
int ins,mem[maxn],inm,root;
template<class T>
inline T read(T &n){
n=;int t=;double x=;char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-:n=ch-'';
for(ch=getchar();isdigit(ch);ch=getchar()) n=n*+ch-'';
if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'')/x,x*=;
return (n*=t);
}void update(int x){tree[x].siz=tree[tree[x].son[]].siz+tree[tree[x].son[]].siz+;}int merge(int x,int y){if(!x||!y) return x+y;
if(tree[x].key<tree[y].key){tree[x].son[]=merge(tree[x].son[],y);update(x);return x;}
else{tree[y].son[]=merge(x,tree[y].son[]);update(y);return y;}
}void split1(int now,int k,int &x,int &y){if(!now){x=y=;return;}
if(tree[now].val<=k){x=now;split1(tree[now].son[],k,tree[now].son[],y);}
else{y=now;split1(tree[now].son[],k,x,tree[now].son[]);}update(now);
}void split2(int now,int k,int &x,int &y){if(!now){x=y=;return;}update(now);
if(k>tree[tree[now].son[]].siz){x=now;
split2(tree[now].son[],k-tree[tree[now].son[]].siz-,tree[now].son[],y);}
else{y=now;split2(tree[now].son[],k,x,tree[now].son[]);}update(now);
}void insert(int x){int u=(inm?mem[inm--]:++ins);
tree[u].key=rand();tree[u].val=x;tree[u].siz=;
int a,b;split1(root,x,a,b);root=merge(merge(a,u),b);
}void delet(int x){int a,b,c,d,e,f;
split1(root,x,a,b);split1(a,x-,c,d);split2(d,,e,f);
mem[++inm]=e;tree[e].res();root=merge(merge(c,f),b);
}int finrnk(int x){int a,b,c;split1(root,x-,a,b);c=tree[a].siz+;root=merge(a,b);return c;}
int finnum(int &rot,int x){int a,b,c,d,e;split2(rot,x-,a,b);
split2(b,,c,d);e=tree[c].val;rot=merge(a,merge(c,d));return e;
}int las(int x){int a,b,c;split1(root,x-,a,b);c=finnum(a,tree[a].siz);root=merge(a,b);return c;}
int nex(int x){int a,b,c;split1(root,x,a,b);c=finnum(b,);root=merge(a,b);return c;}
int main(){
read(n);
fui(i,,n,){
int opt,x;read(opt);read(x);
switch(opt){
case :insert(x);break;
case :delet(x);break;
case :cout<<finrnk(x)<<endl;break;
case :cout<<finnum(root,x)<<endl;break;
case :cout<<las(x)<<endl;break;
case :cout<<nex(x)<<endl;break;
}
}
return ;
}

AC代码

FHQ Treap的其他作用

最重要的一点是它可以代替区间操作!而且支持可持久化!!!

区间操作

将每个点按它们的下标作为关键字,其他的像普通FHQ Treap就行了。

区间翻转的话,每次merge和split都pushdown一下。

洛谷【模板】文艺平衡树(Splay)

 #include<bits/stdc++.h>
using namespace std;
#define INF 0x7fffffff
#define ME 0x7f
#define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
#define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
#define fel(i,a) for(register int i=h[a];i;i=ne[i])
#define ll long long
#define MEM(a,b) memset(a,b,sizeof(a))
#define maxn (100000+10)
int n,m;
struct Node{
int key,val;
int siz,son[];
char iz;
Node(){key=val=siz=son[]=son[]=iz=;}
Node(int x,int y){key=x,val=y,siz=,son[]=son[]=iz=;}
}tree[maxn];
int root;
int l,r;
int rnd(){static int seed=;return seed=int(seed*48271LL%(~0u>>));}
template<class T>
inline T read(T &n){
n=;int t=;double x=;char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-:n=ch-'';
for(ch=getchar();isdigit(ch);ch=getchar()) n=n*+ch-'';
if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'')/x,x*=;
return (n*=t);
}
void update(int x){tree[x].siz=tree[tree[x].son[]].siz+tree[tree[x].son[]].siz+;}
void pushdown(int x){
if(tree[x].iz){
tree[x].iz=;swap(tree[x].son[],tree[x].son[]);
tree[tree[x].son[]].iz^=;tree[tree[x].son[]].iz^=;
}
}
int merge(int x,int y){
if(!x||!y) return x+y;pushdown(x);pushdown(y);
if(tree[x].key<tree[y].key){tree[x].son[]=merge(tree[x].son[],y);update(x);return x;}
else{tree[y].son[]=merge(x,tree[y].son[]);update(y);return y;}
}
void split(int now,int k,int &x,int &y){
if(!now){x=y=;return;}pushdown(now);
if(tree[tree[now].son[]].siz>=k){y=now;split(tree[now].son[],k,x,tree[now].son[]);}
else{x=now;split(tree[now].son[],k-tree[tree[now].son[]].siz-,tree[now].son[],y);}
update(now);
}
void dfs(int now){
pushdown(now);
if(tree[now].son[]) dfs(tree[now].son[]);
printf("%d ",tree[now].val);
if(tree[now].son[]) dfs(tree[now].son[]);
}
int main(){
read(n);read(m);
fui(i,,n,){tree[i]=(Node){rnd(),i};root=merge(root,i);}
fui(i,,m,){
read(l);read(r);int a,b,c;
split(root,r,a,c);split(a,l-,a,b);
tree[b].iz^=;
root=merge(merge(a,b),c);
}
dfs(root);
return ;
}

AC代码

可持久化

还没折腾出来。。。最近也没时间折腾了。。。来日再说吧。。。

FHQ Treap摘要的更多相关文章

  1. fhq treap最终模板

    新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...

  2. NOI 2002 营业额统计 (splay or fhq treap)

    Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每 ...

  3. 【POJ2761】【fhq treap】A Simple Problem with Integers

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

  4. 【fhq Treap】bzoj1500(听说此题多码上几遍就能不惧任何平衡树题)

    1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 15112  Solved: 4996[Submit][Statu ...

  5. 「FHQ Treap」学习笔记

    话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...

  6. FHQ Treap小结(神级数据结构!)

    首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右 ...

  7. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  8. 浅谈fhq treap

    一.简介 fhq treap 与一般的treap主要有3点不同 1.不用旋转 2.以merge和split为核心操作,通过它们的组合实现平衡树的所有操作 3.可以可持久化 二.核心操作 代码中val表 ...

  9. fhq treap 学习笔记

    序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...

随机推荐

  1. java动态获取上传文件的编码类型

    package com.sjfl.main; import java.io.BufferedReader; import java.io.File; import java.io.FileInputS ...

  2. 搭建Github博客:开始

    先看效果:ious.ml 记录使用hexo搭建个人博客的过程 至于在博客里记录什么内容,现在还没想好.已经熟悉了博客园,不想换. 1.概念 Github Pages Github Pages可以被认为 ...

  3. Spark常见问题汇总

    原文地址:https://my.oschina.net/tearsky/blog/629201 摘要: 1.Operation category READ is not supported in st ...

  4. vs2010 vs2013等vs中如何统计整个项目的代码行数

    在一个大工程中有很多的源文件和头文件,我如何快速统计总行数? ------解决方案--------------------b*[^:b#/]+.*$^b*[^:b#/]+.*$ ctrl + shif ...

  5. Linux文件权限设置

    基本概念 https://linux.cn/article-7418-1.html#3_8880 用户管理 文件权限设置 -添加用户账户08% -理解 /etc/passwd 中的内容12% -理解 ...

  6. Session 快速开始 通过session的attribute通信

    [web.xml] <session-config> <session-timeout>30</session-timeout> <cookie-config ...

  7. sql库连sql中间库连orcle库增删改查方案

    ---中间库建立存储过程create procedure Proc_exec@SQL nvarchar(MAX)ASexec(@SQL) GO---web服务器执行语句 --查 select * fr ...

  8. 微信小程序 后端用Flask实现

    手上有个微信小程序项目,因为对Python相对熟悉一些,打算后端用python写,具体采用python 轻量级的flask框架. 在做的过程中,有些问题需要考虑,记录在下边. 1. 开发的小程序后端怎 ...

  9. .Net Core 配置文件appsettings

    1.配置文件为appsettings 在appsettings添加ConnectionStrings: { "Logging": { "IncludeScopes&quo ...

  10. Print Article(斜率DP入门+单调队列)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3507 题目大意:给你n个数,然后问你怎么分割当前的这n个数位那几组,使得每一组的权值加起来最大.每一组 ...