【旋转】

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

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

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. vmware 已将该虚拟机配置为使用 64 位客户机操作系统。但是,无法执行 64 位操作。

    错误提示:已将该虚拟机配置为使用 64 位客户机操作系统.但是,无法执行 64 位操作. 此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态. 如果已在 BIOS/固件设置中禁用 ...

  2. p2 碰撞

    P2可以实现物体碰撞模拟,同时在碰撞过程中派发一些事件实现碰撞检测,将碰撞信息及时反馈,以添加相应的特效. P2中,当两个刚体的最小包围盒AABB发生重叠,碰撞就开始了:然后刚体的形状发生重叠,同时P ...

  3. 卸载Visual Studio最佳方法难道真的是重装系统?

    卸载Visual Studio最佳方法难道真的是重装系统? 卸载Visual Studio最佳方法难道真的是重装系统? 使用TotalUninstaller貌似也没有效果,默认卸载的,程序列表里面还是 ...

  4. UVA11324_The Largest Clique

    极大团.即求一个最大点集,使得点集中的任意两个点u,v至少存在u->v,或者v->u的路径. 是这样做的,求出所有的联通分量,然后整个图就变成了无环图,把原来若干个点缩点,点权为分量的点数 ...

  5. Atcoder arc080E Young Maids(线段树+优先队列)

    给出一个n排列,每次可以选择相邻的两个数字放在新的排列首部,问最后形成的新的排列字典序最小是? 考虑新排列的第一个数字,则应是下标为奇数的最小数,下标不妨设为i.第二个数字应该下标大于i且为偶数的最小 ...

  6. Merge Two Sorted Lists - LeetCode

    目录 题目链接 注意点 解法 小结 题目链接 Merge Two Sorted Lists - LeetCode 注意点 两个链表长度可能不一致 解法 解法一:先比较两个链表长度一致的部分,多余的部分 ...

  7. BZOJ 4316: 小C的独立集 解题报告

    4316: 小C的独立集 Description 图论小王子小C经常虐菜,特别是在图论方面,经常把小D虐得很惨很惨. 这不,小C让小D去求一个无向图的最大独立集,通俗地讲就是:在无向图中选出若干个点, ...

  8. sqlmap利用DNS进行oob(out of band)注入(转)

      0x00 起因 实际案子的时候遇到了一个注入,过狗可以使用sqlmap,但是是基于时间的注入和限制频率需要使用--delay参数,本来就是延时再加上--delay等的心力憔悴.所有有了下面介绍使用 ...

  9. Android Progurad 代码混淆

    ref: ProGuard基础语法和打包配置.mdhttps://github.com/D-clock/Doc/blob/master/Android/Gradle/3_ProGuard%E5%9F% ...

  10. Codeforces Good Bye 2018

    咕bye 2018,因为我这场又咕咕咕了 无谓地感慨一句:时间过得真快啊(有毒 A.New Year and the Christmas Ornament 分类讨论后等差数列求和 又在凑字数了 #in ...