放在前面的话

本蒟蒻因为最近的题目总是搞点奇奇怪怪的平衡树,就去学了下\(Treap\)

现在来总结一下

由于本人是个蒟蒻,本文可能有部分错误,麻烦各位读者大佬在评论区提醒

什么是\(Treap\)

\(Treap\)取自两个单词,一是\(tree\),一是\(heap\)

也就是说,\(Treap\)结合了二叉搜索树和堆

\(Treap\)维持平衡的方法

方法就是

随 机 数!!!

不要不信,真的是随机数

对于每个点,\(Treap\)给予它们一个随机数

并要求在满足二叉搜索树的基础上,随机数要形成一个大(小)根堆


接下来将给出一道模板题,\(Treap\)的操作将在模板题的讲解中给出

例题讲解

放例题

\(Treap\)例题

讲解

数组

\(size[i]\)表示以\(i\)为根的子树的大小

\(num[i]\)表示值为\(i\)的个数

\(val[i]\)表示节点\(i\)的值

\(son[i][0/1]\)表示\(i\)的左/右儿子

\(rd[i]\)表示节点\(i\)的随机值

这些数组在接下来会多次提及,各位读者大佬可以稍稍记忆一下

操作

统计(\(pushup\))

重新统计以\(i\)为根的子树的大小

当前大小:左儿子的大小+右儿子的大小+当前这个数的个数

void pushup(int p)
{
size[p]=size[son[p][0]]+size[son[p][1]]+num[p];
}

旋转(\(rot\))!!!

\(Treap\)的核心操作

分为左旋和右旋,但是思路一样,故一起介绍

旋转的目的是将一个子节点移到父亲处,在过程中满足二叉搜索树的性质

以右旋为例

一开始是这样的



现在我们要将\(B\)转到\(A\)那里

怎么搞呢???

根据二叉搜索树的性质我们知道

\(B<A\)

那么可以把\(A\)丢给\(B\)当右儿子

但是\(B\)已经有了右儿子\(y\)了

再想,根据二叉搜索树有\(B<y<A\)

那么\(y\)就可以丢给\(A\)当左儿子

然后\(B\)的左儿子和\(A\)的右儿子不变

旋转完了之后



检查一下大小关系

旋转前:\(x<B<y<A<z\)

旋转后:\(x<B<y<A<z\)

一模一样

具体操作呢

上代码

void rot(int &p,int d)
{
int k=son[p][d^1];
son[p][d^1]=son[k][d];
son[k][d]=p;
pushup(p);
pushup(k);
p=k;
}

\(d\)为0是左旋,为1右旋

以\(d=1\)为例(右旋)

     a
/ \
b z
/ \
x y

\(k\)为\(p\)的左儿子

先把\(k\)的右儿子丢给\(p\)当左儿子:

son[p][d^1]=son[k][d];

现在长这样

              a(p)
/ \
b(k) y z
/
x

再把\(p\)丢给\(k\)当右儿子

son[k][d]=p;

变成了这样

     b(k)
/ \
x a(p)
/ \
y z

再\(pushup(p和k)\)

最后\(p=k\)

结束

那么我们就可以用旋转来维护堆了

插入(\(ins\))

要插入一个数\(x\)

可以一直判断\(x\)与当前节点的大小关系,选择往哪边递归

直到找到一棵空子树就把\(x\)放进去

放进去之后看一下\(rd\)值的大小,有不对的就旋转

插入后重新统计大小

void ins(int &p,int x)
{
if (!p)
{
sum++;
p=sum;
size[p]=num[p]=1;
val[p]=x;
rd[p]=rand();
return;
}
if (val[p]==x)
{
num[p]++;
size[p]++;
return;
}
int d=(x>val[p]);
ins(son[p][d],x);
if (rd[p]<rd[son[p][d]]) rot(p,d^1);
pushup(p);
}

删除(\(del\))

跟插入差不多

\(x<val[p]\)往左边走

\(x>val[p]\)往右边走

有点不同的是在\(x=val[p]\)的时候

分4种情况讨论

  1. 无左儿子和右儿子
  2. 无左儿子
  3. 无右儿子
  4. 有左儿子和右儿子

情况1:删自己

情况2:左旋,往左边走

情况3:右旋,往右边走

情况4:看哪边的\(rd\)值大,就旋转哪边,往那边走

删除完之后重新统计一下大小

void del(int &p,int x)
{
if (!p) return;
if (x<val[p]) del(son[p][0],x);
else if (x>val[p]) del(son[p][1],x);
else
{
if (!son[p][0]&&!son[p][1])
{
num[p]--;
size[p]--;
if (!num[p]) p=0;
}
else if (!son[p][1])
{
rot(p,1);
del(son[p][1],x);
}
else if (!son[p][0])
{
rot(p,0);
del(son[p][0],x);
}
else
{
int d=(rd[son[p][0]]>rd[son[p][1]]);
rot(p,d);
del(son[p][d],x);
}
}
pushup(p);
}

查询排名(\(get\_rank\))

如果没有这个数,返回0

如果\(val[p]=x\),输出左儿子的大小+1

如果\(val[p]>x\),往左儿子走

如果\(val[p]<x\),往右儿子走,并输出左儿子的大小+\(num[x]\)+\(x\)在右儿子中的排名

int get_rank(int p,int x)
{
if (!p) return 0;
if (val[p]==x) return (size[son[p][0]]+1);
if (val[p]<x) return (size[son[p][0]]+num[p]+get_rank(son[p][1],x));
if (val[p]>x) return get_rank(son[p][0],x);
}

查询值(\(get\_sum\))

如果\(size[son[p][0]>x\) 往左边走

如果\(size[son[p][0]+num[p]<x\) 往右边走,在右边查找排名\(x-size[son[p][0]-num[p]\)的数

若都不满足,返回\(val[p]\)

int get_sum(int p,int x)
{
if (!p) return 0;
if (size[son[p][0]]>=x) return get_sum(son[p][0],x);
else if (size[son[p][0]]+num[p]<x) return get_sum(son[p][1],x-size[son[p][0]]-num[p]);
else return val[p];
}

查询前驱(\(get\_pre\))

如果当前\(p\)为0返回\(-∞\)(一定要特别小)

如果\(val[p]>=x\),即在左儿子中,那就往左边走

否则的话返回当前值和右儿子中的前驱里大的那个(所以为什么要特别小)

int get_pre(int p,int x)
{
if (!p) return -inf;
if (val[p]>=x) return get_pre(son[p][0],x);
else return max(val[p],get_pre(son[p][1],x));
}

查询后继(\(get\_suc\))

跟查询前驱类似

只不过为0的时候返回\(∞\),因为后面是\(min\)

左儿子和右儿子换一下就可以

int get_suc(int p,int x)
{
if (!p) return inf;
if (val[p]<=x) return get_suc(son[p][1],x);
else return min(val[p],get_suc(son[p][0],x));
}

到此所有的操作都已讲解完毕

Code——总

#include<cstdio>
#include<stdlib.h>
#include<iostream>
#define inf 2147483647
using namespace std;
int n,pd,x,s,sum,size[100005],son[100005][3],val[100005],num[1000005],rd[100005];
void pushup(int p)
{
size[p]=size[son[p][0]]+size[son[p][1]]+num[p];
}
void rot(int &p,int d)
{
int k=son[p][d^1];
son[p][d^1]=son[k][d];
son[k][d]=p;
pushup(p);
pushup(k);
p=k;
}
void ins(int &p,int x)
{
if (!p)
{
sum++;
p=sum;
size[p]=num[p]=1;
val[p]=x;
rd[p]=rand();
return;
}
if (val[p]==x)
{
num[p]++;
size[p]++;
return;
}
int d=(x>val[p]);
ins(son[p][d],x);
if (rd[p]<rd[son[p][d]]) rot(p,d^1);
pushup(p);
}
void del(int &p,int x)
{
if (!p) return;
if (x<val[p]) del(son[p][0],x);
else if (x>val[p]) del(son[p][1],x);
else
{
if (!son[p][0]&&!son[p][1])
{
num[p]--;
size[p]--;
if (!num[p]) p=0;
}
else if (!son[p][1])
{
rot(p,1);
del(son[p][1],x);
}
else if (!son[p][0])
{
rot(p,0);
del(son[p][0],x);
}
else
{
int d=(rd[son[p][0]]>rd[son[p][1]]);
rot(p,d);
del(son[p][d],x);
}
}
pushup(p);
}
int get_rank(int p,int x)
{
if (!p) return 0;
if (val[p]==x) return (size[son[p][0]]+1);
if (val[p]<x) return (size[son[p][0]]+num[p]+get_rank(son[p][1],x));
if (val[p]>x) return get_rank(son[p][0],x);
}
int get_sum(int p,int x)
{
if (!p) return 0;
if (size[son[p][0]]>=x) return get_sum(son[p][0],x);
else if (size[son[p][0]]+num[p]<x) return get_sum(son[p][1],x-size[son[p][0]]-num[p]);
else return val[p];
}
int get_pre(int p,int x)
{
if (!p) return -inf;
if (val[p]>=x) return get_pre(son[p][0],x);
else return max(val[p],get_pre(son[p][1],x));
}
int get_suc(int p,int x)
{
if (!p) return inf;
if (val[p]<=x) return get_suc(son[p][1],x);
else return min(val[p],get_suc(son[p][0],x));
}
int main()
{
freopen("104.in","r",stdin);
scanf("%d",&n);
while (n--)
{
scanf("%d%d",&pd,&x);
if (pd==1) ins(s,x);
if (pd==2) del(s,x);
if (pd==3) printf("%d\n",get_rank(s,x));
if (pd==4) printf("%d\n",get_sum(s,x));
if (pd==5) printf("%d\n",get_pre(s,x));
if (pd==6) printf("%d\n",get_suc(s,x));
}
return 0;
}

教学之Treap的更多相关文章

  1. Yeoman 官网教学案例:使用 Yeoman 构建 WebApp

    STEP 1:设置开发环境 与yeoman的所有交互都是通过命令行.Mac系统使用terminal.app,Linux系统使用shell,windows系统可以使用cmder/PowerShell/c ...

  2. Linux实战教学笔记08:Linux 文件的属性(上半部分)

    第八节 Linux 文件的属性(上半部分) 标签(空格分隔):Linux实战教学笔记 第1章 Linux中的文件 1.1 文件属性概述(ls -lhi) linux里一切皆文件 Linux系统中的文件 ...

  3. Linux实战教学笔记07:Linux系统目录结构介绍

    第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...

  4. Linux实战教学笔记06:Linux系统基础优化

    第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...

  5. Linux实战教学笔记05:远程SSH连接服务与基本排错(新手扫盲篇)

    第五节 远程SSH连接服务与基本排错 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 远程连接LInux系统管理 1.1 为什么要远程连接Linux系统 在实际的工作场景中,虚拟机界面或物理 ...

  6. Linux实战教学笔记04:Linux命令基础

    第四节:Linux命令基础 标签(空格分隔):Linux实战教学笔记 第1章 认识操作环境 root:当前登陆的用户名 @分隔符 chensiqi:主机名 -:当前路径位置 用户的提示符 1.1 Li ...

  7. Linux实战教学笔记03:操作系统发展历程及系统版本选择

    标签(空格分隔): Linux实战教学笔记-陈思齐 第1章 Linux简介 1.1 什么是操作系统? 简单讲:操作系统就是一个人与计算机硬件的中介. 操作系统,英文名称Operating System ...

  8. Linux实战教学笔记02:计算机系统硬件核心知识

    标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 互联网企业常见服务器介绍 1.1 互联网公司服务器品牌 - DELL(大多数公司,常用) - HP - IBM(百度在用) 浪潮 联想 航天联 ...

  9. Linux实战教学笔记01:计算机硬件组成与基本原理

    标签(空格分隔): Linux实战教学笔记 第1章 如何学习Linux 要想学好任何一门学问,不仅要眼睛看,耳朵听,还要动手记,勤思考,多交流甚至尝试着去教会别人. 第2章 服务器 2.1 运维的基本 ...

随机推荐

  1. POJ2432 Around the world

    题意描述 Around the world 在一个圆上有 \(n\) 点,其中有 \(m\) 条双向边连接它们,每条双向边连接两点总是沿着圆的最小弧连接. 求从 \(1\) 号点出发并回到 \(1\) ...

  2. (二)http请求方法和状态码

    1.HTTP请求方法 根据 HTTP 标准,HTTP 请求可以使用多种请求方法. HTTP1.0 定义了三种请求方法: GET.POST 和 HEAD方法. HTTP1.1 新增了六种请求方法:OPT ...

  3. Python之使用pip安装三方库Error:Could not find a version that satisfies the requirement <package>(from versions: none),No matching distribution found for <package>

    出现多次使用pip安装包时提示以下报错: ERROR: Could not find a version that satisfies the requirement <package> ...

  4. .net 实现签名验签

    本人被要求实现.net的签名验签,还是个.net菜鸡,来分享下采坑过程 依然,签名验签使用的证书格式依然是pem,有关使用openssl将.p12和der转pem的命令请转到php实现签名验签 .ne ...

  5. Centos7系统kvm虚机忘记密码进不去, 通过宿主机修改/etc/shadow文件改密码,重启后系统起不来故障排错

    问题描述 某天, 因为其他项目组交接问题, kvm里面的堡垒机系统用户root密码登录不上,然后他通过宿主机修改/etc/shadow文件修改密码,但是修改完后重启系统后发现kvm宿主机连接不上虚机了 ...

  6. 【译】关于Rust模块的清晰解释

    原文链接: http://www.sheshbabu.com/posts/rust-module-system/ 原文标题: Clear explanation of Rust's module sy ...

  7. Spring笔记(8) - @EventListener注解探究

    在上文中讲了Spring的事件监听机制,流程是:定义事件.监听器,发布事件,控制台输出监听到的事件内容. 在上文的扩展中 使用 @EventListener 注解来自定义监听器,监听指定的事件,比如下 ...

  8. 重构rbd镜像的元数据

    这个已经很久之前已经实践成功了,现在正好有时间就来写一写,目前并没有在其他地方有类似的分享,虽然我们自己的业务并没有涉及到云计算的场景,之前还是对rbd镜像这一块做了一些基本的了解,因为一直比较关注故 ...

  9. echarts折线图,数据切换时(最近七天)绘图不合理现象

    echarts折线图,当进行数据切换时存在绘制不合理的问题,数据没错,但绘制不对. 两个0之间的连线应该是平滑直线,如图: 正确的显示: 解决: 在myCharts.setOption(option) ...

  10. C#高级编程之泛型一(泛型的引入、泛型的使用、何为泛型)

    为何引入泛型 当我们要对不同类型的参数执行类似的方法时:如下所示功能打印传入参数的相关信息. class CommonMethdod { /// <summary> /// show in ...