原理

以随机数维护平衡,使树高期望为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. python模块之logging模块

    1. 低配版 # 指定显示信息格式 import logging logging.basicConfig( level=20, # 设置显示或写入的起始级别 format="%(asctim ...

  2. Kibana 搜索语法

    Kibana 搜索语法 Kibana 支持三种搜索语法, 分别是 Lucene query 语法, 基于 json 的 ES query语法, 以及 Kuery 语法. 前两种语法可以直接使用, Ku ...

  3. ****** 四十二 ******、软设笔记【网络基础】-IPv6协议、常用的网络协议

    一.IPv6协议 IPv6协议,全称"互联网协议第6版",即下一代的网际协议. 相对于IPv4来说,IPv6协议主要改进: *扩展的地址.IPv6地址长度为128位. *IPv6使 ...

  4. [译]使用NuGet管理共享代码

    原文 可以在内网部署自己的私人NuGet仓储服务. Setting it up 本例中我们创建一个发邮件的类,将其作为我们自己的NuGet包: using System; using System.N ...

  5. tensorflow/core/kernels/conv_ops.cc:659] check failed: stream->parent()->getc

    tensorflow版本与cudnn版本不对应,如tensorflow-gpu==1.1.0,cudnn==6.0 升级tensorflow-gpu到更高版本

  6. VGG-16详解

    VGG16输入224*224*3的图片,经过的卷积核大小为3x3x3,stride=1,padding=1,pooling为采用2x2的max pooling方式: 1.输入224x224x3的图片, ...

  7. Docker(一)基本概念

    摘自 https://mp.weixin.qq.com/s/mcIMBMNMrFD9OE56iujhXQ 一.容器和虚拟机的比较 1.虚拟机 对于以前熟悉的虚拟机,我们需要模拟操作系统和硬件.虚拟机一 ...

  8. kindeditor<=4.1.5 文件上传漏洞利用

    kindeditor<=4.1.5 文件上传漏洞 - Kindeditor <=4.1.5 file upload vulnerability and use 漏洞存影响版本:小于等于4. ...

  9. LinkedQueue的底层实现

    package zy813ture; public class MyLinkedQueue1 { private Node head; private Node last = head = new N ...

  10. 练习:javascript弹出框及地址选择功能,可拖拽

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...