题目传送门

  转载自https://www.cnblogs.com/fengzhiyuan/articles/7994428.html,转载请注明出处

  

Treap 简介

  Treap 是一种二叉查找树。它的结构同时满足二叉查找树(Tree)与堆(Heap)的性质,因此得名。Treap的原理是为每一个节点赋一个随机值使其满足堆的性质,保证了树高期望 O(log2n) ,从而保证了时间复杂度。 
  Treap 是一种高效的平衡树算法,在常数大小与代码复杂度上好于 Splay。

Treap 的基本操作

  现在以 BZOJ 3224 普通平衡树为模板题,详细讨论 Treap 的基本操作。

1.基本结构

  在一般情况下,Treap 的节点需要存储它的左右儿子,子树大小,节点中相同元素的数量(如果没有可以默认为1),自身信息及随机数的值。

struct node{
int l, r, v, siz, rnd, ct;
}d[];
其中 l 为左儿子节点编号, r 为右儿子节点编号, v 为节点数值, siz 为子树大小, rnd 为节点的随机值, ct为该节点数值的出现次数(目的为将所有数值相同的点合为一个)。

 

2.关于随机值

  随机值由 rand() 函数生成, 考虑到 <cstdlib> 库中的 rand() 速度较慢,所以在卡常数的时候建议手写 rand() 函数。

inline int rand(){
static int seed = ;
return seed = (int)((((seed ^ ) + 19260817ll) * 19890604ll) % );
}

其中 seed 为随机种子,可以随便填写。

3.节点信息更新

  节点信息更新由 update() 函数实现。在每次产生节点关系的修改后,需要更新节点信息(最基本的子树大小,以及你要维护的其他内容)。 
  时间复杂度 O(1) 。

inline void update(int k){
d[k].siz = d[lc].siz + d[rc].siz + d[k].ct;
}

4.「重要」左旋与右旋

  左旋与右旋是 Treap 的核心操作,也是 Treap 动态保持树的深度的关键,其目的为维护 Treap 堆的性质。 
  下面的图片可以让你更好的理解左旋与右旋:

  

  下面具体介绍左旋与右旋操作。左旋与右旋均为变更操作节点与其两个儿子的相对位置的操作。 
  「左旋」为将作儿子节点代替根节点的位置, 根节点相应的成为左儿子节点的右儿子(满足二叉搜索树的性质)。相应的,之前左儿子节点的右儿子应转移至之前根节点的左儿子。此时,只有之前的根节点与左儿子节点的 siz 发生了变化。所以要 update() 这两个节点。 
  「右旋」类似于「左旋」,将左右关系相反即可。 
  时间复杂度 O(1) 。

void rturn(int &k){ //右旋
int t = d[k].l; d[k].l = d[t].r; d[t].r = k;
d[t].siz = d[k].siz; update(k); k = t;
} void lturn(int &k){ //左旋
int t = d[k].r; d[k].r = d[t].l; d[t].l = k;
d[t].siz = d[k].siz; update(k); k = t;
}

 

5.节点的插入与删除

  节点的插入与删除是 Treap 的基本功能之一。 
  「节点的插入」是一个递归的过程,我们从根节点开始,逐个判断当前节点的值与插入值的大小关系。如果插入值小于当前节点值,则递归至左儿子;大于则递归至右儿子;

  相等则直接在把当前节点数值的出现次数 +1 ,跳出循环即可。如果当前访问到了一个空节点,则初始化新节点,将其加入到 Treap 的当前位置。 
  「节点的删除」同样是一个递归的过程,不过需要讨论多种情况: 
  如果插入值小于当前节点值,则递归至左儿子;大于则递归至右儿子。 
  如果插入值等于当前节点值: 
    若当前节点数值的出现次数大于 1 ,则减一; 
    若当前节点数值的出现次数等于于 1 : 
      若当前节点没有左儿子与右儿子,则直接删除该节点(置 0); 
      若当前节点没有左儿子或右儿子,则将左儿子或右儿子替代该节点; 
      若当前节点有左儿子与右儿子,则不断旋转 当前节点,并走到当前节点新的对应位置,直到没有左儿子或右儿子为止。 
  时间复杂度均为 O(log2n) 。 
  具体实现代码如下:

void ins(int &p,int x)
{
if (p==)
{
p=++sz;
tr[p].siz=tr[p].ct=,tr[p].val=x,tr[p].rnd=rand();
return;
}
tr[p].siz++;
if (tr[p].val==x) tr[p].ct++;
else if (x>tr[p].val)
{
ins(tr[p].r,x);
if (tr[rs].rnd<tr[p].rnd) lturn(p);
}else
{
ins(tr[p].l,x);
if (tr[ls].rnd<tr[p].rnd) rturn(p);
}
}
void del(int &p,int x)
{
if (p==) return;
if (tr[p].val==x)
{
if (tr[p].ct>) tr[p].ct--,tr[p].siz--;//如果有多个直接减一即可。
else
{
if (ls==||rs==) p=ls+rs;//单节点或者空的话直接儿子移上来或者删去即可。
else if (tr[ls].rnd<tr[rs].rnd) rturn(p),del(p,x);
else lturn(p),del(p,x);
}
}
else if (x>tr[p].val) tr[p].siz--,del(rs,x);
else tr[p].siz--,del(ls,x);
}

6.查询数x的排名

  查询数x的排名可以利用在二叉搜索树上的相同方法实现。 
  具体思路为根据递归找到当前节点,并记录小于这个节点的节点的数量(左子树) 。 
  时间复杂度 O(log2n) 。 
  代码实现如下:

int find_pm(int p,int x)
{
if (p==) return ;
if (tr[p].val==x) return tr[ls].siz+;
if (x>tr[p].val) return tr[ls].siz+tr[p].ct+find_pm(rs,x);
else return find_pm(ls,x);
}

7.查询排名为x的数

  查询排名为x的数可以利用在二叉搜索树上的相同方法实现。 
  具体思路为根据当前x来判断该数在左子树还是右子树 。 
  时间复杂度 O(log2n) 。 
  代码实现如下:

int find_hj(int p,int x)
{
if (p==) return inf;
if (tr[p].val<=x) return find_hj(rs,x);
else return min(tr[p].val,find_hj(ls,x));
}

8.查询数的前驱与后继

  查询数的前驱与后继同样可以递归实现。查前驱即为递归当前数,走到小于等于x的节点,并记录其中最大的。后继同理。 
  时间复杂度 O(log2n) 。 
  代码实现如下:

  

int find_qq(int p,int x)
{
if (p==) return -inf;
if (tr[p].val<x) return max(tr[p].val,find_qq(rs,x));
else if (tr[p].val>=x) return find_qq(ls,x);
}
int find_hj(int p,int x)
{
if (p==) return inf;
if (tr[p].val<=x) return find_hj(rs,x);
else return min(tr[p].val,find_hj(ls,x));
}

下面放洛谷的模板题的代码:  

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=;
int n,ans,tot,root;
struct Treap{
int val,l,r;
int id,size,cnt;
}t[maxn];
inline int read()
{
char ch=getchar();int num=;bool flag=false;
while(ch<''||ch>''){if(ch=='-')flag=true;ch=getchar();}
while(ch>=''&&ch<=''){num=num*+ch-'';ch=getchar();}
return flag?-num:num;
}
inline void update(int k)
{t[k].size=t[t[k].l].size+t[t[k].r].size+t[k].cnt;}
inline void left_rotate(int &k)
{
int y=t[k].r;t[k].r=t[y].l;t[y].l=k;
t[y].size=t[k].size;update(k);k=y;
}
inline void right_rotate(int &k)
{
int y=t[k].l;t[k].l=t[y].r;t[y].r=k;
t[y].size=t[k].size;update(k);k=y;
}
inline void ins(int &k,int x)
{
if(k==){
++tot;k=tot;t[k].val=x;
t[k].size=t[k].cnt=;
t[k].id=rand();return;
}
++t[k].size;
if(t[k].val==x)++t[k].cnt;
else if(x>t[k].val){
ins(t[k].r,x);
if(t[t[k].r].id<t[k].id)
left_rotate(k);
}
else{
ins(t[k].l,x);
if(t[t[k].l].id<t[k].id)
right_rotate(k);
}
}
inline void del(int &k,int x)
{
if(k==)return;
if(t[k].val==x){
if(t[k].cnt>){
--t[k].cnt;--t[k].size;
return;}
if(t[k].l*t[k].r==)
k=t[k].l+t[k].r;
else if(t[t[k].l].id<t[t[k].r].id)
right_rotate(k),del(k,x);
else left_rotate(k),del(k,x);
}
else if(x>t[k].val)
--t[k].size,del(t[k].r,x);
else --t[k].size,del(t[k].l,x);
}
inline int fid(int k,int x)
{
if(k==)return ;
if(t[k].val==x)return t[t[k].l].size+;
else if(x>t[k].val)
return t[t[k].l].size+t[k].cnt+fid(t[k].r,x);
else return fid(t[k].l,x);
}
inline int fin(int k,int x)
{
if(k==)return ;
if(x<=t[t[k].l].size)
return fin(t[k].l,x);
else if(x>t[t[k].l].size+t[k].cnt)
return fin(t[k].r,x-t[t[k].l].size-t[k].cnt);
else return t[k].val;
}
inline void pred(int k,int x)
{
if(k==)return;
if(t[k].val<x){
ans=k;pred(t[k].r,x);
}
else pred(t[k].l,x);
}
inline void sucd(int k,int x)
{
if(k==)return;
if(t[k].val>x){
ans=k;sucd(t[k].l,x);
}
else sucd(t[k].r,x);
}
int main()
{
n=read();
for(int i=;i<=n;i++){
int caozuo=read();
int x=read();ans=;
switch(caozuo){
case :ins(root,x);break;
case :del(root,x);break;
case :printf("%d\n",fid(root,x));break;
case :printf("%d\n",fin(root,x));break;
case :{pred(root,x);printf("%d\n",t[ans].val);break;}
case :{sucd(root,x);printf("%d\n",t[ans].val);break;}
}
}
return ;
}

洛谷P3369普通平衡树(Treap)的更多相关文章

  1. [洛谷P3369] 普通平衡树 Treap & Splay

    这个就是存一下板子...... 题目传送门 Treap的实现应该是比较正经的. 插入删除前驱后继排名什么的都是平衡树的基本操作. #include<cstdio> #include< ...

  2. 洛谷P3369 普通平衡树

    刚学平衡树,分别用了Splay和fhq-treap交了一遍. 这是Splay的板子,貌似比较短? Splay #include <iostream> #include <cstdio ...

  3. 洛谷P3369 【模板】普通平衡树(Treap/SBT)

    洛谷P3369 [模板]普通平衡树(Treap/SBT) 平衡树,一种其妙的数据结构 题目传送门 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入x数 删除 ...

  4. 【洛谷P3369】【模板】普通平衡树题解

    [洛谷P3369][模板]普通平衡树题解 题目链接 题意: 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3 ...

  5. [洛谷P3391] 文艺平衡树 (Splay模板)

    初识splay 学splay有一段时间了,一直没写...... 本题是splay模板题,维护一个1~n的序列,支持区间翻转(比如1 2 3 4 5 6变成1 2 3 6 5 4),最后输出结果序列. ...

  6. 绝对是全网最好的Splay 入门详解——洛谷P3369&BZOJ3224: Tyvj 1728 普通平衡树 包教包会

    平衡树是什么东西想必我就不用说太多了吧. 百度百科: 一个月之前的某天晚上,yuli巨佬为我们初步讲解了Splay,当时接触到了平衡树里的旋转等各种骚操作,感觉非常厉害.而第二天我调Splay的模板竟 ...

  7. BZOJ3223/洛谷P3391 - 文艺平衡树

    BZOJ链接 洛谷链接 题意 模板题啦~2 代码 //文艺平衡树 #include <cstdio> #include <algorithm> using namespace ...

  8. BZOJ3224/洛谷P3391 - 普通平衡树(Splay)

    BZOJ链接 洛谷链接 题意简述 模板题啦~ 代码 //普通平衡树(Splay) #include <cstdio> int const N=1e5+10; int rt,ndCnt; i ...

  9. 洛谷 P3391 文艺平衡树

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 --b ...

随机推荐

  1. Elastic Search操作入门

    前言 Elastic Search是基于Lucene这个非常成熟的索引方案,另加上一些分布式的实现:集群,sharding,replication等.具体可以参考我同事写的文章. 本文主要介绍ES入门 ...

  2. vs 自定义插件(扩展工具)

    此篇仅仅是因为好奇,实现的是完全没有价值的东西,当然,通过此篇的尝试,后续可以在适当的场景,深入的研究Visual Studio自定义插件的应用. 实现功能如下: 在鼠标选中的地方,显示一下创建人,创 ...

  3. 在Linux系统里运行shutdown.sh命令关闭Tomcat时出现错误提示

    服务器:linnux 5.5 64位,已安装好 jdk: Tomcat版本:apache-tomcat-7.0.53 操作软件:Xshell 4(Free for Home / School) 刚开始 ...

  4. 【LuoguP3038/[USACO11DEC]牧草种植Grass Planting】树链剖分+树状数组【树状数组的区间修改与区间查询】

    模拟题,可以用树链剖分+线段树维护. 但是学了一个厉害的..树状数组的区间修改与区间查询.. 分割线里面的是转载的: ----------------------------------------- ...

  5. 【hdu3033】分组背包(每组最少选一个)

    [题意] 有S款运动鞋,一个n件,总钱数为m,求不超过总钱数且每款鞋子至少买一双的情况下,使价值最大.如果有一款买不到,就输出“Impossible". 1<=N<=100  1 ...

  6. 网络应用框架Netty快速入门

    一 初遇Netty Netty是什么? Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.可扩展协议的服务器和客户端 ...

  7. python3中处理url异常

    import urllib.request import urllib.error url = 'http://c.telunyun.com/Chart/getJsonData?market=1' d ...

  8. SQL注入之逗号拦截绕过

    目前所知博主仅知的两个方法 1.通过case when then 2.join [一]case when then mysql,,,,,,, ) ) end; +----+-----------+-- ...

  9. 【Python学习笔记】Coursera课程《Using Databases with Python》 密歇根大学 Charles Severance——Week4 Many-to-Many Relationships in SQL课堂笔记

    Coursera课程<Using Databases with Python> 密歇根大学 Week4 Many-to-Many Relationships in SQL 15.8 Man ...

  10. centos_7.1.1503_src_7

    http://vault.centos.org/7.1.1503/os/Source/SPackages/ tex-fonts-hebrew-0.1-21.el7.src.rpm 05-Jul-201 ...