题目传送门

  转载自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. 常见一个新的maven web工程

    使用Eclipse创建一个新的maven Web应用工程,步骤如下: 1.在Elipse中新建一个maven工程,点击next: 2.选择工程路径(此处使用默认的),点击next: 3.选择Arche ...

  2. poi-对于word的操作(二)

    poi对于word文本的底纹和下划线的样式的展现 package poi.test; import java.io.FileOutputStream; import java.math.BigInte ...

  3. 耐心排序Patience Sorting

    这个排序的关键在建桶和入桶规则上 建桶规则:如果没有桶,新建一个桶;如果不符合入桶规则那么新建一个桶 入桶规则:只要比桶里最上边的数字小即可入桶,如果有多个桶可入,那么按照从左到右的顺序入桶即可 举个 ...

  4. 洛谷金秋夏令营模拟赛 第2场 T11738 伪神

    调了一个下午只有八十分QAQ md弃了不管了 对拍也没拍出来 鬼知道是什么数据把我卡了QAQ 没事我只是个SB而已 这题其实还是蛮正常的 做法其实很简单 根据链剖的构造方法 你每次修改都是一段又一段的 ...

  5. CSS(Cascading Style Shee)

    1.CSS是Cascading Style Sheet这个几个英文单词的缩写,翻译成中文是“层叠样式表”的意思 CSS能让网页制作者有效的定制.改善网页的效果. CSS是对HTML的补充,网页设计师曾 ...

  6. H5特性 MutationObserver 监听元素 动态改变iframe高度

    这些代码要写在iframe页中执行 <script type="text/javascript"> $(function () { // Firefox和Chrome早 ...

  7. js_数组去重效率对比

    学习javascript已经快两年了,也不知道到了什么程度了. 说说我对javascript的理解,在电脑的世界里,只有数据. 数组,对象,字符串.对这些数据进行操作就可以完成很多业务逻辑,和页面的交 ...

  8. 使用 ftrace 调试 Linux 内核【转】

    转自:http://blog.csdn.net/adaptiver/article/details/7930646 使用 ftrace 调试 Linux 内核,第 1 部分 http://blog.c ...

  9. c++设计模式系列----factory模式

    问题: 假设我们要开发一个游戏--打怪物,首先,游戏有分等级,假设有初级,中级两个个等级(就不用flappy bird模式了,那个比较特殊,对一个玩家来说是难以具有持久吸引力的!),不同的等级怪物也是 ...

  10. 【LOJ2254】SNOI2017一个简单的询问

    莫队,每次询问的是两个区间,就把区间拆开,分开来算就好了. 借鉴了rank1大佬的玄学排询问的姿势. #include<bits/stdc++.h> #define N 50010 typ ...