问题引入:Luogu P3369 普通平衡树

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x x x 数
  2. 删除 x x x 数(若有多个相同的数,因只删除一个)
  3. 查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1 )
  4. 查询排名为 x x x 的数
  5. 求 x x x 的前驱(前驱定义为小于 x x x,且最大的数)
  6. 求 x x x 的后继(后继定义为大于 x x x,且最小的数)

拿到这道题,你觉得暴力该怎么做?
v e c t o r vector vector 二分查找 + 暴力插入可以拿到 60 60 60 分,为什么会只有 60 60 60 呢?思考一下,我们的瓶颈在于插入是 O ( n ) O(n) O(n) 的,那么什么数据结构可以做到复杂度很低的插入呢? 那就是

考虑建立一颗二叉搜索树,对于任意节点 p p p 满足 v a l l c p < v a l p < v a l r c p val_{lc_p}<val_p<val_{rc_p} vallcp​​<valp​<valrcp​​ ,其中 l c p lc_p lcp​ 与 r c p rc_p rcp​ 分别指 p p p 的左儿子和右儿子。

那么给出一组测试数据,看看建成的树会是什么样子:

给出操作 4 4 4 的代码:

inline int findxth(int root,int x)
{if(siz[L[root]]+1==x) return root;if(siz[L[root]]>=x) return findkth(L[root],x);return findkth(R[root],x-siz[L[root]]-1);
}

在代码中, s i z siz siz 树组保存的是子树的大小, L L L 与 R R R 分别储存做儿子与右儿子的下标,所以当前数在子树中的排名是 s i z L r o o t + 1 siz_{L_{root}}+1 sizLroot​​+1,那么分类讨论:

  1. x ≤ s i z L r o o t x\leq siz_{L_{root}} x≤sizLroot​​,那么以 r o o t root root 为根的的子树中,第 x x x 大是其左子树上的第 x x x 大;
  2. x = s i z L r o o t + 1 x=siz_{L_{root}}+1 x=sizLroot​​+1,那么以 r o o t root root 为根的的子树中,第 x x x 大就是 r o o t root root
  3. x > s i z L r o o t + 1 x> siz_{L_{root}}+1 x>sizLroot​​+1,那么那么以 r o o t root root 为根的的子树中,第 x x x 大是其左子树上的第 x − s i z L r o o t − 1 x-siz_{L_{root}}-1 x−sizLroot​​−1 大。

思考一下为什么最后是 x − s i z L r o o t − 1 x-siz_{L_{root}}-1 x−sizLroot​​−1:其实是因为前 s i z L r o o t + 1 siz_{L_{root}}+1 sizLroot​​+1 个都在左子树与根上。

那么怎么卡掉这种数据结构呢?

每次插入数单调递增或递减,那么就会退化成这样的链:

那么复杂度就退化为 O ( n ) O(n) O(n) 的了,还不如写个暴力。

那我们怎么解决这种情况呢? 我们需要代入堆的性质:
对于一个堆,根节点的权一定小于任意儿子的权。

怎么把这个性质代入二叉搜索树中呢?对于普通的 T r e a p Treap Treap ,我们引入两种旋转操作,左旋与右旋,每个节点赋予一个随机的权,通过不断的左旋与右旋,满足其堆的性质,并且保留其二叉搜索树的性质,那么这棵树是期待平衡的。

但在这片文章中,我们不讨论左旋与右旋,运用大佬 F h q Fhq Fhq 自创的 F h q − T r e a p Fhq-Treap Fhq−Treap ,来维护其堆的性质。

先来看其核心操作:分裂与合并:

Split 分裂

分裂有两种,分别为 按值分裂按排名分裂

按值分裂

按值分裂指把一颗树按给定的权值 p o s pos pos 分裂成两颗子树,我们称为 左树右树,满足:左树上的权值都小于等于 p o s pos pos ,右树上的权值大于 p o s pos pos,那么怎么处理呢?

比如在当前树中:

我们按 p o s = 15 pos=15 pos=15 分割:
首先,这个东西肯定是递归的,我们先尝试对根处理:
24 > 15 24>15 24>15 ,那么它一定属于右树的:

我们突然现,因为 24 > 15 24>15 24>15 ,由于二叉搜索树的性质,右儿子的权值总是比根大,那么 24 24 24 的右子树上的所有权值一定是大于 15 15 15 的,那么它们都属于右子树的:


我们现在只需要对 24 24 24 的左子树进行操作:


发现 13 < 15 13<15 13<15,那么 13 13 13 的左子树上的权值也一定小于 15 15 15,那么分裂树变成这样:

对 13 13 13 的右子树,即 14 14 14 进行判断,那么分裂结果如下:

给出递归代码:

inline void split(int p,int v,int &l,int &r){if(!p) {l=r=0;return;}//没有了,返回else if(val[p]<=v) l=p,split(R[p],v,R[p],r);//当前节点属于左树,那么左子树也一定属于左树,只需在右子树中再递归判断else r=p,split(L[p],v,l,L[p]);//同理upd(p);
}

此处 l , r l,r l,r 是引用的,那么只需在外定义两个整数,在分裂时当做参数传入,执行完 l , r l,r l,r 即为左右树的根。
F h q − t r e a p Fhq-treap Fhq−treap 的好处在于你只需要想好一半,另一半只需要复制粘贴再改为相反的即可。

注: u p d upd upd 函数是用来维护子树大小的,给出代码:

inline void upd(int p)
{siz[p]=siz[L[p]]+siz[R[p]]+1;
}

按排名分裂

给出代码,原理同上给出操作 4 4 4 的代码,自行思考:

inline void split(int p, int k, int &x, int &y)
{if(!p){x = y = 0;return;}else if(r <= siz[L[p]]) y=p,split(L[p],k,x,L[p]);else x=id,split(R[p],k-siz[L[p]]-1,R[p],y);upd(p);
}

那么 F h q − T r e a p Fhq-Treap Fhq−Treap 核心代码已经写完,接下来切题

Luogu P3369 普通平衡树:

  1. 插入一个数 x x x
inline void ins(int a)
{split(rt,a,x,y);cre(tmp,a);rt=merge(merge(x,tmp),y);
}

按值 a a a 分裂为两个树,创立新节点,节点序号为 t m p tmp tmp(此处为引用),值为 a a a。
给出 c r e cre cre 创建节点代码:

inline void cre(int &p,int v){val[++tot]=v;siz[tot]=1;rand(rd[tot]);p=tot;
}

t o t tot tot 为当前的节点数, r a n d rand rand 中的参数为引用的,即为给其赋予一个伪随机值,给出 r a n d rand rand 函数代码:

int rnd=1345252;
void rand(int &x){x=(rnd=(rnd*123044)%1000000000);}
  1. 删除一个数 x x x
inline void del(int a){split(rt,a,x,y);split(x,a-1,x,z);z=merge(L[z],R[z]);rt=merge(merge(x,z),y);
}

经过两次分裂, z z z 树上所有节点权值都为 x x x,因为只需要删除一个,那么考虑删除根,合并左右节点,记得再合并回去,此处 r t rt rt 为整树的根

  1. 查询 x x x 数的排名
inline int findrank(int a){split(rt,a-1,x,y);tmp=siz[x]+1;rt=merge(x,y);return tmp;
}

分裂后左树上所有节点权小于 x x x ,右树上所有节点权值大于等于 x x x ,那么 x x x 的排名即为左树大小 + 1 +1 +1

  1. 与二叉搜索树原理相同
inline int findkth(int p,int k){if(siz[L[p]]+1==k) return p;if(siz[L[p]]>=k) return findkth(L[p],k);return findkth(R[p],k-siz[L[p]]-1);
}
  1. 找 x x x 数的前驱
inline int front(int a){split(rt,a-1,x,y);tmp=findkth(x,siz[x]);rt=merge(x,y);return tmp;
}

即为分裂后左树(左树权值小于 x x x) 中,排名为左树大小的权值

  1. 找 x x x 数的后继
inline int back(int a){split(rt,a,x,y);tmp=findkth(y,1);rt=merge(x,y);return tmp;
}

思路与 5 5 5 无异

注意:分裂后一定要合并回来

给出完整代码:

#include <stdio.h>
#define Maxn 200004
int L[Maxn],R[Maxn];
int val[Maxn],siz[Maxn],rd[Maxn],tmp;
int rt,tot,x,y,z,rnd=1345252;
void rand(int &x){x=(rnd=(rnd*123044)%1000000000);}
inline void upd(int p){siz[p]=siz[L[p]]+siz[R[p]]+1;
}
inline void cre(int &p,int v){val[++tot]=v;siz[tot]=1;rand(rd[tot]);p=tot;
}
inline void split(int p,int v,int &l,int &r){if(!p) {l=r=0;return;}else if(val[p]<=v) l=p,split(R[p],v,R[p],r);else r=p,split(L[p],v,l,L[p]);upd(p);
}
inline int merge(int l,int r){if(!l||!r) return l+r;if(rd[l]<rd[r]){R[l]=merge(R[l],r);upd(l);return l;}L[r]=merge(l,L[r]);   upd(r);return r;
}
inline void ins(int a){split(rt,a,x,y);cre(tmp,a);rt=merge(merge(x,tmp),y);
}
inline void del(int a){split(rt,a,x,y);split(x,a-1,x,z);z=merge(L[z],R[z]);rt=merge(merge(x,z),y);
}
inline int findrank(int a){split(rt,a-1,x,y);tmp=siz[x]+1;rt=merge(x,y);return tmp;
}
inline int findkth(int p,int k){if(siz[L[p]]+1==k) return p;if(siz[L[p]]>=k) return findkth(L[p],k);return findkth(R[p],k-siz[L[p]]-1);
}
inline int front(int a){split(rt,a-1,x,y);tmp=findkth(x,siz[x]);rt=merge(x,y);return tmp;
}
inline int back(int a){split(rt,a,x,y);tmp=findkth(y,1);rt=merge(x,y);return tmp;
}
int main(){int Q,opt,a;scanf("%d",&Q);while(Q--){scanf("%d%d",&opt,&a);if(opt==1) ins(a);else if(opt==2) del(a);else if(opt==3) printf("%d\n",findrank(a));else if(opt==4) printf("%d\n",val[findkth(rt,a)]);else if(opt==5) printf("%d\n",val[front(a)]);else printf("%d\n",val[back(a)]);}
}

浅谈 Fhq-Treap相关推荐

  1. 浅谈二叉搜索树(BST)

    浅谈BST 前言 最近不顺心的事情有点多,再加上赶ptaL2 的题单,很久没做知识总结了 ,现在pta的题目告一段落,参考了某大佬 (某卷王) 总结的知识点,鸣谢大佬!总结一下BST问题的知识点,供以 ...

  2. 浅谈MySQL存储引擎-InnoDBMyISAM

    浅谈MySQL存储引擎-InnoDB&MyISAM 存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取.MySQL存储引擎有很多,不同的存储引擎保存数据和索引的方式 ...

  3. 【大话设计模式】——浅谈设计模式基础

    初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化 ...

  4. 学校计算机机房好处,浅谈学校计算机机房维护

    浅谈学校计算机机房维护    现在的学校机房都配置了数量较多的计算机,而且机房的使用非常频繁.对于怎样维护好计算机,特别是计算机软件系统,对广大计算机教师来说是一个很重要且非常现实的问题.下面就本人在 ...

  5. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  6. mybatis与php,浅谈mybatis中的#和$的区别

    浅谈mybatis中的#和$的区别 发布于 2016-07-30 11:14:47 | 236 次阅读 | 评论: 0 | 来源: 网友投递 MyBatis 基于Java的持久层框架MyBatis 本 ...

  7. 浅谈GCC预编译头技术

    浅谈GCC预编译头技术 文/jorge --谨以此文,悼念我等待MinGW编译时逝去的那些时间. 其 实刚开始编程的时候,我是丝毫不重视编译速度之类的问题的,原因很简单,因为那时我用BASICA.后来 ...

  8. 【笔记】震惊!世上最接地气的字符串浅谈(HASH+KMP)

    震惊!世上最接地气的字符串浅谈(HASH+KMP) 笔者过于垃圾,肯定会有些错的地方,欢迎各位巨佬指正,感激不尽! 引用:LYD的蓝书,一本通,DFC的讲稿,网上各路巨佬 Luguo id: 章鱼那个 ...

  9. 浅谈几种区块链网络攻击以及防御方案之其它网络攻击

    旧博文,搬到 csdn 原文:http://rebootcat.com/2020/04/16/network_attack_of_blockchain_other_attack/ 写在前面的话 自比特 ...

  10. 浅谈几种区块链网络攻击以及防御方案之拒绝服务攻击

    旧博文,搬到 csdn 原文:http://rebootcat.com/2020/04/14/network_attack_of_blockchain_ddos_attack/ 写在前面的话 自比特币 ...

最新文章

  1. 10大经典排序算法,20+张图就搞定
  2. python 红黑树_python学习笔记|红黑树(性质与插入)
  3. ip6tables 无法基于端口过滤IPv6 分片报文问题解决
  4. 页面中color颜色值_HTML+CSS 基础知识-入门概括-颜色与单位
  5. linux之cal命令详解,linux命令大全之cal命令详解(显示日历)
  6. Oracle 该用户下所有的表
  7. laravel缓存html,Laravel 静态页面缓存 JosephSilber/page-cache - 码农天地
  8. 用Python一次性把论文作图与数据处理全部搞定!
  9. QQ号终于可以当传家宝了!没车没房的,就留几个游戏账号给儿子吧?
  10. CCF NOI1064 计算斐波那契第n项
  11. 电脑位数怎么看_怎么用电脑播放4K视频?怎么用电脑看4K电影? 附带详细教程...
  12. 大数据分析有什么特点
  13. mysql 首次连接慢_mybatis+mysql,第一次数据库连接很慢怎么回事?
  14. [转载]python模块学习---HTMLParser(解析HTML文档元素)
  15. 想买个这样的笔记本电脑
  16. Uipath文档教程
  17. 陌上红尘,可有摆渡人?
  18. java 根据经纬度换算距离
  19. 【LaTeX教程】九.Latex常见数学公式模板
  20. IPAD 安装win8 苏菲安装Mac OS iphone安装安卓

热门文章

  1. 电脑桌面右下方点击失灵,其他桌面区域正常
  2. docker之cgroup、compose
  3. 遥感图像预处理与土地利用动态监测
  4. 静态路由和默认路由的原理与基础配置命令
  5. 行锁、间隙锁、next-key锁
  6. Python pandas读取excel单元格数据,显示不全问题
  7. 信息系统面临的安全风险
  8. 网络层协议介绍与概述
  9. Apollo planning之PiecewiseJerkPathOptimizer
  10. 必示科技2020年度告警数据挖掘方向顶会论文分享