【旋转】

平衡树中的旋转是指在不改变中序遍历的前提下改变树的形态的方式。(中序遍历=排名顺序)

右旋将当前点的左节点旋上来,左旋反之。(图侵删)

void rturn(int &k){
int o=t[k].l;
t[k].l=t[o].r;
t[o].r=k;
up(k);up(o);
k=o;
}

原根k,新根o。

1.把k的左节点o解放出来并更新为o的右节点。

2.解放出来的o成为新根,其右孩子赋为k。

【Treap】树堆

功能:维护支持单点插入和单点删除的排名树。

特点:给每个节点随机堆权,在维持排名不变的前提下通过旋转维护堆结构,从而能使高度平衡。

Treap的左子树<=根节点,右子树>根节点。

操作:

1.查找数的排名或位置:判断与k的大小关系,往左或往右(累加sz),相等时找到数的位置。

2.查找指定排名的数字:判断与t[t[k].l].sz+1的大小关系,往左或往右,相等时找到数的位置。

3.插入:查找数的位置,相等往左走直到空位置插入,回溯时维护堆性质。

4.删除:查找数的位置,相等时比较左右孩子堆权后旋转到底部删除。

5.前驱:查找数的位置,为其左子树的最右孩子,若不存在则为其父亲,若不是父亲的右孩子则无解(第一个数)。

6.后继:同上,右子树的最左孩子,否则为父亲。

注意:要时时维护堆性质,否则速度严重变慢。

例题和模版:【BZOJ】3224: Tyvj 1728 普通平衡树

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
struct tree{int l,r,sz,rnd,num;}t[maxn*];
int n,sz,root;
void up(int k){t[k].sz=+t[t[k].l].sz+t[t[k].r].sz;}
void lturn(int &k){
int o=t[k].r;
t[k].r=t[o].l;
t[o].l=k;
up(k);up(o);
k=o;
}
void rturn(int &k){
int o=t[k].l;
t[k].l=t[o].r;
t[o].r=k;
up(k);up(o);
k=o;
}
void ins(int &k,int x){
if(!k){k=++sz;t[k].rnd=rand();t[k].num=x;t[k].sz=;return;}//return
t[k].sz++;
if(x<=t[k].num){
ins(t[k].l,x);
if(t[t[k].l].rnd<t[k].rnd)rturn(k);//turn
}
else{
ins(t[k].r,x);
if(t[t[k].r].rnd<t[k].rnd)lturn(k);
}
}
void del(int &k,int x){
//t[k].sz--;
if(t[k].num==x){
if(t[k].l*t[k].r==){k=t[k].l+t[k].r;return;}
if(t[t[k].l].rnd<t[t[k].r].rnd){
rturn(k);
t[k].sz--;
del(t[k].r,x);
}
else{
lturn(k);
t[k].sz--;
del(t[k].l,x);
}
}
else if(x<t[k].num)t[k].sz--,del(t[k].l,x);else t[k].sz--,del(t[k].r,x);
}
int find(int k,int x){
if(!k)return ;//!k
if(x<=t[k].num)return find(t[k].l,x);
else return t[t[k].l].sz++find(t[k].r,x);
}
int rank(int k,int x){
if(t[t[k].l].sz+==x)return t[k].num;
if(x<t[t[k].l].sz+)return rank(t[k].l,x);
else return rank(t[k].r,x-t[t[k].l].sz-);
}
int pre(int k,int x){
if(!k)return -;
if(t[k].num<x)return max(t[k].num,pre(t[k].r,x));
else return pre(t[k].l,x);
}
int suc(int k,int x){
if(!k)return 0x3f3f3f3f;
if(t[k].num>x)return min(t[k].num,suc(t[k].l,x));
else return suc(t[k].r,x);
}
int main(){
srand();//srand!!!!!!!!!!
scanf("%d",&n);sz=root=;
for(int i=;i<=n;i++){
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==)ins(root,x);
if(opt==)del(root,x);
if(opt==)printf("%d\n",find(root,x)+);//+1
if(opt==)printf("%d\n",rank(root,x));
if(opt==)printf("%d\n",pre(root,x));
if(opt==)printf("%d\n",suc(root,x));
}
return ;
}

【fhq-treap】可分裂合并的treap

真正的范浩强treap其实并不仅仅如此,这里使用这个名字只是为了方便。

功能:可分裂合并的treap,通过split和merge两个操作可以实现平衡树的所有功能(可以取代splay)。

(只有单点操作时仍然建议使用普通treap,fhq-treap步骤多所以常数大)

操作:

1.合并(merge):按照a左b右合并两棵平衡树,对两个树根比较堆权,将较小者放在上面后继续递归合并。

2.分裂(split):将平衡树按排名分裂成k和n-k两棵平衡树,通过判断当前根属于左树或右树后递归进行,传参得到最后的a和b。

3.查找区间:对于区间[a,b],将平衡树分裂成三部分,查询后合并。

4.线性建树:少用,做法是对最右一列维护栈,每次插入到右下角(栈顶),通过小型旋转维护堆性质并将栈顶相应弹出,最后记得t[0].r=0。

细节:

1.必须保证全过程不要影响到t[0]的值,t[0]的初始值也对全过程没有影响

2.平衡树的上传必须考虑本身

例题和模板:【BZOJ】1251: 序列终结者

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
struct tree{int l,r,rnd,num,mx,add,delta,sz;}t[maxn*];
int st[maxn];
int n,m,root;
void down(int k){
if(t[k].delta){
swap(t[k].l,t[k].r);
if(t[k].l)t[t[k].l].delta^=;
if(t[k].r)t[t[k].r].delta^=;
t[k].delta=;
}
if(t[k].add){
int p=t[k].add;
if(t[k].l)t[t[k].l].add+=p,t[t[k].l].mx+=p,t[t[k].l].num+=p;
if(t[k].r)t[t[k].r].add+=p,t[t[k].r].mx+=p,t[t[k].r].num+=p;//keep 0->0!!!
t[k].add=;
}
}
int max(int a,int b){return a<b?b:a;}
void up(int k){//different from sgt!
t[k].mx=max(t[k].num,max(t[t[k].l].mx,t[t[k].r].mx));
t[k].sz=+t[t[k].l].sz+t[t[k].r].sz;
}
void dfs(int k){
if(!k)return;
dfs(t[k].l);
dfs(t[k].r);
up(k);
}
void build(){
int top=;
for(int i=;i<=n;i++){
t[i]=(tree){,,rand(),,,,,};
while(top&&t[st[top]].rnd>t[i].rnd){
t[st[top]].r=t[i].l;
t[i].l=st[top--];
}
t[st[top]].r=i;
st[++top]=i;
}
dfs(st[]);
t[].r=;
t[].mx=-0x3f3f3f3f;//make 0 no influence
root=st[];
}
int merge(int a,int b){
if(!a||!b)return a^b;
if(t[a].rnd<t[b].rnd){
down(a);
t[a].r=merge(t[a].r,b);
up(a);
return a;
}
else{
down(b);
t[b].l=merge(a,t[b].l);
up(b);
return b;
}
}
void split(int k,int &l,int &r,int x){
if(!k)return void(l=r=);
down(k);
if(x<t[t[k].l].sz+){
r=k;
split(t[k].l,l,t[k].l,x);
}
else{
l=k;
split(t[k].r,t[k].r,r,x-t[t[k].l].sz-);
}
up(k);
}
void plus(int l,int r,int x){
int a,b,c;
split(root,b,c,r);
split(b,a,b,l-);
t[b].add+=x;t[b].mx+=x;t[b].num+=x;
root=merge(a,b);root=merge(root,c);
}
void turn(int l,int r){
int a,b,c;
split(root,b,c,r);split(b,a,b,l-);
t[b].delta^=;
root=merge(a,b);root=merge(root,c);
}
int ask(int l,int r){
int a,b,c,ans;
split(root,b,c,r);split(b,a,b,l-);
ans=t[b].mx;
root=merge(a,b);root=merge(root,c);
return ans;
}
int main(){
srand();
scanf("%d%d",&n,&m);
build();
for(int i=;i<=m;i++){
int k,l,r,x;
scanf("%d%d%d",&k,&l,&r);
if(l>r)continue;
if(k==){
scanf("%d",&x);
plus(l,r,x);
}
else if(k==)turn(l,r);
else printf("%d\n",ask(l,r));
}
return ;
}
【splay】很久以前学的splay,后来全部改用fhq-treap了,当时的笔记就丢这里了,有需自取。

Treap和Splay学习小结:http://blog.csdn.net/jtjy568805874/article/details/50734723

旋转不改变中序遍历,也就是不改变序列的顺序(以序列下标作为关键字)。

splay维护的是序列的顺序!

用find(root,l)和find(t[root][],k)把区间旋转到root右孩子的左孩子处,注意哨兵

记得有哨兵!

<Node>申请新结点。(从1开始,虚根为0)//最开始第一个点怎么办?

<build>fa &x申请新节点 l r
fa初始进来是0。
申请结点,若非叶子就然后进入左右,最后count。
<count>通过上传更新该点一系列信息。
<rotate>x 定方向,处理y孩子(双向),处理y父亲(双向),处理y新父亲,处理x儿子,全部弄完后更新x的信息(直接从y继承,因为一模一样),count(y)。 <splay>x,y 将x旋转到y的位置(双旋就是一次往上跳两个节点) 判一次旋,定两个方向,双旋。 因为fa,t全部在rotate更新了,不需要传递参数,唯一有可能变化的是root,在find的时候传参就可以了。 <find>&x k 表示找到第k的结点并把它旋转到x结点处。(可以选择不附加splay操作) 所以可以用find(root,l)和find(t[root][],r)把区间旋转到root右孩子的左孩子处。 可以写非递归版,从根(&x)找下去,找到后splay到x处就可以了,双旋作用就体现在这里,防卡。 最后记得x=i; <insert>&x 传进来root。 读入要插入的序列并把它建树,得到一个根节点r。 find(x,l)把l旋转到根,find(t[x][],)把l右边的结点全部旋转到根的右孩子的右孩子处。 然后把r插入根的右孩子的左孩子处,更新fa[r],count一下根极其右孩子即可。 没有结点就是0 <翻转>打标记并先完成自身的修改(其实没什么要修改的),下传时反转左右子树。 <★模板>【BZOJ】: [NOI2005]维修数列:http://www.cnblogs.com/onioncyc/p/6916479.html 修改可以直接修改,splay维护的是序列顺序。 移动可以想成删除再加入。 每次插入都无脑splay到根,可以保证复杂度。 当你前方是万丈深渊,你感到恐惧而无路可走的时候,千万不要放弃,坚定地选择住一个方向,硬着头皮走下去,千万不要对于每个方向都浅尝辄止,否则就会一片迷茫,把握不住它的真实存在,坚定的选择一个方向抗过去,路的那边一定柳暗花明。 就是说,当你学一个东西一直学不会,就抓住一份代码死啃,一定能理解的O_O。 最强平衡树——Treap[以我的最弱击败你的最强]:http://blog.csdn.net/lemonoil/article/details/71816386 平衡树之splay讲解:http://www.cnblogs.com/BLADEVIL/p/3464458.html

splay

【专题】平衡树(Treap,fhq-treap)的更多相关文章

  1. LOJ#105. 文艺平衡树(FHQ Treap)

    题面 传送门 题解 \(FHQ\ Treap\)比起\(Splay\)还是稍微好写一点--就是老是忘了要下穿标记-- //minamoto #include<bits/stdc++.h> ...

  2. 洛谷P3369 【模板】普通平衡树(FHQ Treap)

    题面 传送门 题解 写了一下\(FHQ\ Treap\) //minamoto #include<bits/stdc++.h> #define R register #define inl ...

  3. 非旋treap (fhq treap) 指针版

    传送门 看了一圈,好像真的没什么用指针的呢.. 明明觉得指针很好看(什么??你说RE???听不见听不见) 其实我觉得用数组的话不RE直接WA调起来不是更困难嘛,毕竟通过gdb还可以知道哪里RE,WA就 ...

  4. 洛谷P5055 【模板】可持久化文艺平衡树(FHQ Treap)

    题面 传送门 题解 日常敲板子.jpg //minamoto #include<bits/stdc++.h> #define R register #define inline __inl ...

  5. 洛谷P3835 【模板】可持久化平衡树(FHQ Treap)

    题面 传送门 题解 可持久化一下就好了,具体可以看代码 这里有一个小\(trick\)就是我们原本在\(merge\)的时候也要新建节点的,但是我们\(merge\)之前一般已经\(split\)过了 ...

  6. 可持久化Treap(fhq Treap,非旋转式Treap)学习(未完待续)

    简介:     Treap,一种表现优异的BST 优势:     其较于AVL.红黑树实现简单,浅显易懂     较于Splay常数小,通常用于树套BST表现远远优于Splay     或许有人想说S ...

  7. 非旋Treap——fhq treap

    https://www.luogu.org/problemnew/show/P3369 知识点:1.拆分split,合并merge 2.split,merge要点:通过传址调用来简便代码 3.记得ro ...

  8. 2021.12.08 平衡树——FHQ Treap

    2021.12.08 平衡树--FHQ Treap http://www.yhzq-blog.cc/fhqtreapzongjie/ https://www.cnblogs.com/zwfymqz/p ...

  9. 可持久化treap(FHQ treap)

    FHQ treap 的整理 treap = tree + heap,即同时满足二叉搜索树和堆的性质. 为了使树尽可能的保证两边的大小平衡,所以有一个key值,使他满足堆得性质,来维护树的平衡,key值 ...

  10. 「学习笔记」 FHQ Treap

    FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...

随机推荐

  1. gitlab修改root密码

    在root用户下,执行 [root@localhost gitlab]# sudo gitlab-rails console production -------------------------- ...

  2. 工作中常用到的Linux命令

    ps: (ps的参数分成basic, list, output, thread, miscellaneous) (basic) -e / -A 显示所有进程 (output) -o 输出指定字段 ls ...

  3. Best Time to Buy and Sell Stock IV

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  4. Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...

  5. Java多线程(五) —— 线程并发库之锁机制

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...

  6. mock测试SpringMVC controller报错

    使用mock测试Controller时报错如下 java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig at org.spr ...

  7. YARN与MapReduce1的对比

    Apache YARN (Yet Another Resource Negotiator)从Hadoop2开始.YARN为集群提供资源管理和Applications的调度.YARN的API用于操作集群 ...

  8. BZOJ3240 NOI2013矩阵游戏(数论)

    必修五题. // luogu-judger-enable-o2 #include<iostream> #include<cstdio> #include<cmath> ...

  9. MySQL 大表备份、改表

    0.背景: 需要对一个千万行数据的表新增字段,具体操作: a.dump 数据 b.delete 数据 c.alter 表 MySQL  版本为5.5,alter表时MySQL会锁表:表行数虽多,当数据 ...

  10. 1.红黑树和自平衡二叉(查找)树区别 2.红黑树与B树的区别

    1.红黑树和自平衡二叉(查找)树区别 1.红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单. 2.平衡 ...