P3369 【模板】普通平衡树 Treap树堆学习笔记
文章目录
- Treap = Tree + Heap (树堆 = 树 + 堆)
- 1. 例题1 洛谷P3369 【模板】普通平衡树
- 1. 结构体定义如下
- 2. 向上更新`以u为根的树的节点个数`
- 3. 旋转操作 route
- 4. 插入操作 insert
- 5. 删除操作 del
- 6. 查询排名rand_x
- 7. 查询 K 小值 kth
- 8. 求key的前驱 get_pre
- 9. 求key的后继 get_suf
- 完整代码
- 扩展应用
- 1. 线段树套平衡树,求区间前驱后继排名(就是线段树的每个节点都是一个平衡树)
- 2. 伸展树, 区间反转分割
- 3. 双端优先队列的实现 (可以 取最大最小值的堆)
- 4. 游戏排名系统 (动态数据容器) [原文链接https://blog.csdn.net/yang_yulei/article/details/46005845](https://blog.csdn.net/yang_yulei/article/details/46005845)
Treap = Tree + Heap (树堆 = 树 + 堆)
大佬的博客https://www.luogu.com.cn/blog/HOJQVFNA/qian-xi-treap-ping-heng-shu
简介 : 本质上是一种使用 随机权值维护堆的性质的二叉查找树, 通过基础的树旋转维持堆的性质,
由随机权值保证树高度为 logN
具体做法 : 插入或删除后, 若不满足 堆的性质 则旋转
优点 : 防止 退化成链表 简单好写, 是最好写的平衡树之一, 好理解
缺点 : 常数大
1. 例题1 洛谷P3369 【模板】普通平衡树
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入 x 数 (插入值)
2. 删除 x 数(若有多个相同的数,因只删除一个)
3. 查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
4. 查询排名为 k 的数 (求K小值)
5. 求 x 的前驱 (前驱定义为小于 x,且最大的数)
6. 求 x 的后继 (后继定义为大于 x,且最小的数)
1. 结构体定义如下
#define lson(u) (tree[u].chl[0])
#define rson(u) (tree[u].chl[1])
struct Node {int chl[2], //chl[0]是左儿子 , chl[1]是右儿子val, //节点值sz, //以该节点的为根节点个数recy, //该节点重复了几次rnd; //随机优先级
} tree[MAXN<<4];
2. 向上更新以u为根的树的节点个数
- 当前节点
u
的sz = 左子树sz + 右子树sz +u
的重复次数recy(U)
inline void push_up(int u) {//更新节点以u为根的树的节点个数tree[u].sz = tree[lson(u)].sz + tree[rson(u)].sz + tree[u].recy;
}
3. 旋转操作 route
- 我们可以把左旋和右旋合并成为一个统一的函数
- 通过传入的操作数
cmd
实现相应的旋转
#define LROUTE 0
#define RROUTE 1
void route(int& u, int cmd) { //cmd=0左旋, cmd=1右旋int tmp = tree[u].chl[cmd^1];tree[u].chl[cmd^1] = tree[tmp].chl[cmd];tree[tmp].chl[cmd] = u;push_up(u), push_up(tmp); //不要忘了更新对应的szu = tmp;
}
4. 插入操作 insert
- 和普通
BST
一样的插入, 并建立随机权值即可 - 插入后, 需要向上更新节点的 以该节点的为根节点个数sz
void insert(int& u, int key) {if(!u) { //走到叶子就新建节点u = ++tot;tree[u].sz = tree[u].recy = 1;tree[u].val = key;tree[u].rnd = rand(); //随机权值return ;}tree[u].sz ++;if(key == tree[u].val) { tree[u].recy ++; return ; }int cmd = (key > tree[u].val); //大于则向右插入insert(tree[u].chl[cmd], key);//插入后维护大根堆的性质//失衡的时候, 往与插入相反的方向旋转if(tree[u].rnd < tree[tree[u].chl[cmd]].rnd)route(u, cmd^1);push_up(u);
}
5. 删除操作 del
- 和普通的BST一样的删除, 分为 4种情况
- 没有这值为 key 的节点, 直接返回
- 当前根和key不相等, 就去相应的子节点解决
- 叶子节点, 直接删除
- 有左儿子, 没有右儿子, 右旋并去右儿子解决 (因为右旋后
root
转到了rig
) - 没有左儿子, 有右儿子, 左旋并去左儿子解决(因为左旋后
root
转到了lef
) - 左右都有, 就把 大的转上来 并去 另一个儿子 解决
- 使用 堆删除的方式, 把
key
旋转到叶子 节点后删除
void del(int& u, int key) {if(!u) return ;if(key < tree[u].val) del(lson(u), key);else if(key > tree[u].val) del(rson(u), key);else { //4种删除情况if(!lson(u) && !rson(u)) { //叶子节点直接删除tree[u].sz --,tree[u].recy --;if(tree[u].recy == 0) u = 0;} else if(lson(u) && !rson(u)) { //只有左节点route(u, 1);del(rson(u), key);} else if(!lson(u) && rson(u)) { //只有右节点route(u, 0);del(lson(u), key);} else if(lson(u) && rson(u)) { //左右都有//把大的那个转上来,并去另一个子树解决int cmd = (tree[lson(u)].rnd > tree[rson(u)].rnd);route(u, cmd);del(tree[u].chl[cmd], key);}}push_up(u);
}
6. 查询排名rand_x
- 还是比较
key
, 小于往左, 大于往右 - 往右说明 root的左儿子中的所有节点都 小于key
所以排名应加上 左儿子的节点的个数 和 根节点的重复次数
int rank_x(int u, int key) { //求key的排名if(!u) return 0;if(key == tree[u].val) //返回左子树个数 + 1return tree[lson(u)].sz + 1;else if(key < tree[u].val) //小于就去左子树找return rank_x(lson(u), key);else //大于就去右子树, 类似于权值线段树return tree[lson(u)].sz +tree[u].recy +rank_x(rson(u), key);
}
7. 查询 K 小值 kth
- 如果左子树节点个数大于等于 k , 说明 k 小值还在左子树
- 如果左子树的节点个数加上根的重复次数仍然小于 k, 说明k小值在右子树
int kth(int u, int k) { //查询k小值if(!u) return 0;if(tree[lson(u)].sz >= k) return kth(lson(u), k);else if(tree[lson(u)].sz + tree[u].recy < k)return kth(rson(u), k-tree[lson(u)].sz-tree[u].recy);else return tree[u].val;
}
8. 求key的前驱 get_pre
- 只要根仍然大于等于key, 就向左子树找
- 向右走记得取
max
, 前驱定义为小于key,且最大的数
int get_pre(int u, int key) {if(!u) return -INF;if(key <= tree[u].val) return get_pre(lson(u), key);elsereturn max(tree[u].val, get_pre(rson(u), key));
}
9. 求key的后继 get_suf
int get_suf(int u, int key) {if(!u) return INF;if(key >= tree[u].val) return get_suf(rson(u), key);elsereturn min(tree[u].val, get_suf(lson(u), key));
}
完整代码
// #define debug
#ifdef debug
#include <time.h>
#include "win_majiao.h"
#endif#include <iostream>
#include <algorithm>
#include <vector>
#include <string.h>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <math.h>#define MAXN ((int)1e5+7)
#define ll long long int
#define INF (0x7f7f7f7f)
#define QAQ (0)using namespace std;
typedef vector<vector<int> > VVI;#define show(x...) \do { \cout << "[" << #x << " -> "; \err(x); \} while (0)void err() { cout << "]" << endl; }
template<typename T, typename... A>
void err(T a, A... x) { cout << a << ' '; err(x...); }namespace FastIO{char print_f[105];void read() {}void print() { putchar('\n'); }template <typename T, typename... T2>inline void read(T &x, T2 &... oth) {x = 0;char ch = getchar();ll f = 1;while (!isdigit(ch)) {if (ch == '-') f *= -1; ch = getchar();}while (isdigit(ch)) {x = x * 10 + ch - 48;ch = getchar();}x *= f;read(oth...);}template <typename T, typename... T2>inline void print(T x, T2... oth) {ll p3=-1;if(x<0) putchar('-'), x=-x;do{print_f[++p3] = x%10 + 48;} while(x/=10);while(p3>=0) putchar(print_f[p3--]);putchar(' ');print(oth...);}
} // namespace FastIO
using FastIO::print;
using FastIO::read;int n, m, Q, K, root, tot;#define lson(u) (tree[u].chl[0])
#define rson(u) (tree[u].chl[1])
struct Node {int chl[2], //chl[0]是左儿子 , chl[1]是右儿子val, //节点值sz, //该节点的为根节点个数recy, //该节点重复了几次rnd; //随机优先级
} tree[MAXN<<4];void push_up(int u) {//更新节点以u为根的树的节点个数tree[u].sz = tree[lson(u)].sz + tree[rson(u)].sz + tree[u].recy;
}#define LROUTE 0
#define RROUTE 1
void route(int& u, int cmd) { //cmd=0左旋, cmd=1右旋int tmp = tree[u].chl[cmd^1];tree[u].chl[cmd^1] = tree[tmp].chl[cmd];tree[tmp].chl[cmd] = u;push_up(u), push_up(tmp);u = tmp;
}void insert(int& u, int key) {if(!u) {u = ++tot;tree[u].sz = tree[u].recy = 1;tree[u].val = key;tree[u].rnd = rand(); //随机权值return ;}tree[u].sz ++;if(key == tree[u].val) { tree[u].recy ++; return ; }int cmd = (key > tree[u].val); //大于则向右插入insert(tree[u].chl[cmd], key);//插入后维护大根堆的性质//失衡的时候, 往与插入相反的方向旋转if(tree[u].rnd < tree[tree[u].chl[cmd]].rnd)route(u, cmd^1);push_up(u);
}void del(int& u, int key) {if(!u) return ;if(key < tree[u].val) del(lson(u), key);else if(key > tree[u].val) del(rson(u), key);else { //4种删除情况if(!lson(u) && !rson(u)) { //叶子节点直接删除tree[u].sz --,tree[u].recy --;if(tree[u].recy == 0) u = 0;} else if(lson(u) && !rson(u)) { //只有左节点route(u, 1);del(rson(u), key);} else if(!lson(u) && rson(u)) { //只有右节点route(u, 0);del(lson(u), key);} else if(lson(u) && rson(u)) { //左右都有//把大的那个转上来,并去另一个子树解决int cmd = (tree[lson(u)].rnd > tree[rson(u)].rnd);route(u, cmd);del(tree[u].chl[cmd], key);}}push_up(u);
}int rank_x(int u, int key) { //求key的排名if(!u) return 0;if(key == tree[u].val) //返回左子树个数 + 1return tree[lson(u)].sz + 1;else if(key < tree[u].val) //小于就去左子树找return rank_x(lson(u), key);else //大于就去右子树, 类似于权值线段树return tree[lson(u)].sz +tree[u].recy +rank_x(rson(u), key);
}int kth(int u, int k) { //查询k大值if(!u) return 0;if(tree[lson(u)].sz >= k) return kth(lson(u), k);else if(tree[lson(u)].sz + tree[u].recy < k)return kth(rson(u), k-tree[lson(u)].sz-tree[u].recy);else return tree[u].val;
}int get_pre(int u, int key) {if(!u) return -INF;if(key <= tree[u].val) return get_pre(lson(u), key);elsereturn max(tree[u].val, get_pre(rson(u), key));
}int get_suf(int u, int key) {if(!u) return INF;if(key >= tree[u].val) return get_suf(rson(u), key);elsereturn min(tree[u].val, get_suf(lson(u), key));
}signed main() {#ifdef debugfreopen("test.txt", "r", stdin);clock_t stime = clock();
#endifread(n);int cmd, val, ans;for(int i=1; i<=n; i++) {read(cmd, val);switch (cmd) {case 1:insert(root, val);break;case 2:del(root, val);break;case 3:ans = rank_x(root, val);printf("%d\n", ans);break;case 4:ans = kth(root, val);printf("%d\n", ans);break;case 5:ans = get_pre(root, val);printf("%d\n", ans);break;case 6:ans = get_suf(root, val);printf("%d\n", ans);break;default:break;}}
#ifdef debugclock_t etime = clock();printf("rum time: %lf 秒\n",(double) (etime-stime)/CLOCKS_PER_SEC);
#endif return 0;
}
扩展应用
1. 线段树套平衡树,求区间前驱后继排名(就是线段树的每个节点都是一个平衡树)
2. 伸展树, 区间反转分割
3. 双端优先队列的实现 (可以 取最大最小值的堆)
- 使用Treap实现取出最大最小值即可
4. 游戏排名系统 (动态数据容器) 原文链接https://blog.csdn.net/yang_yulei/article/details/46005845
- 实现三种操作
- 上传一条得分记录
- 查看当前排名
- 返回区间 [L,R] 内的排名记录
P3369 【模板】普通平衡树 Treap树堆学习笔记相关推荐
- Treap树堆(bzoj 3224: Tyvj 1728 普通平衡树)
Treap树堆:一种排序二叉树(中序遍历权值有序) 每个节点有两个关键字:key[]和rand[] 其中key[]满足二叉搜索树性质,rand[]满足堆性质(即Tree+Heap=Treap)即 如果 ...
- 珂朵莉树/ODT 学习笔记
珂朵莉树/ODT 学习笔记 起源自 CF896C.珂朵莉yyds! 核心思想 把值相同的区间合并成一个结点保存在 set 里面. 用处 骗分.只要是有区间赋值操作的数据结构题都可以用来骗分.在数据随机 ...
- Treap(树堆)图解与实现
个人技术博客:http://blog.ztgreat.cn 前面我们介绍了AVL树,伸展树,它们都是二叉搜索树,二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会很大,Treap树就是一种解决二 ...
- Treap树(堆树)
描述 Treap=Tree+Heap(堆树). Treap 是一棵二叉搜索树 树的每个节点存储左指针和右指针,以及一个优先级,这个优先级是建树的时候随机制定的. 树的节点优先级满足堆序性质:任何节点的 ...
- 平衡树(splay)学习笔记(详细,从入门到精(bao)通(ling))(持续更新)
前言 在前几天军训站军姿的时候胡思乱想,突然明白了splay的本质 KMP学习笔记后又一篇字数上万的题解- 前置技能--二叉搜索树 首先来看一个最简单的问题: 你需要维护一个数据结构,资磁这些操作: ...
- BZOJ1112洛谷P3466 [POI2008]KLO-Building blocks(砖头)[对顶堆学习笔记]
坑爹的BZOJ因为不让 输出方案我WA了若干次 据说正解是平衡树/线段树,我不会,就用了个新东西->对顶堆 对顶堆 一个维护中位数的东西,我们通过维护两个堆,来维所有数的中位数 (显然我们可以排 ...
- 带时滞的病毒模型计算模板【基于matlab的动力学模型学习笔记_1】
/*仅当作学习笔记,若有纰漏欢迎友好交流指正,此外若能提供一点帮助将会十分荣幸*/ 摘 要:无论是生物病毒还是网络病毒,其内核的传播机理都有很多的相似之处.因此,本文在经典的SIR病毒模型基础上改造出 ...
- 数据结构-Treap(树堆) 详解
- ACM模板(从小白到ACMer的学习笔记)
写在开头: 2020年ICPC银川站,现场赛,第三题的字典树没能过,首站打铁,不过这一场也让我看到铜牌其实没有想象中的那么难. 2020年ICPC沈阳站,现场赛,封榜后过两题,铜首,I题原本需要黑题难 ...
- 斜堆学习笔记+复杂度证明
和左偏树几乎一模一样,唯一的区别是左偏树合并后判断如果左儿子深度小于右儿子则交换左右儿子,而斜堆直接无脑交换. 复杂度是均摊的 O(nlogn)O(n\log n)O(nlogn) 证明: 定义重结 ...
最新文章
- mysql备份策略的制定
- 虚拟机实现二层交换机_局域网SDN技术硬核内幕 5 虚拟化网络的实现
- 分辨5线单极性步进电机接头
- C#之while与do……while语句
- Windows服务器上怎样开放指定端口
- nodejs path.parse()
- 64位ubuntu16.04下pycharm无法切换fcitx输入法和无法输入中文的问题
- 641. Design Circular Deque
- rnn神经网络模型_ICLR 2019 | 与胶囊网络异曲同工:Bengio等提出四元数循环神经网络...
- 入侵微博服务器刷流量,开发者获刑 5 年;马化腾重回中国首富;支持 M1 芯片,VS Code 1.54 发布 | 极客头条...
- 8个jQuery Mobile基础教程
- HDUacm2095
- TFS2012 权限设置
- LeetCode——字节跳动系列题目
- 一心多用多线程-阻塞队列(5)-CountDownLatch
- 【计算机网络】数据链路层(完整版)
- java研发网页数据采集
- 【视频直播场景下P2P对等网技术①】挑战与形式化分析
- 如何为 Exchange 用户添加联系人头像
- 计算机基础知识教程打印a4纸,学会这9个Word打印方法,不浪费任何一张A4纸,涨知识了...
热门文章
- mysql源码及内核分析,MySQL · 源码阅读 · Secondary Engine
- 浏览器-IE主页被篡改后修复
- python字符串重复子串_LeetCode 459. 重复的子字符串 | Python
- linux c 获取usb vid,Linux使用libudev获取USB设备VID及PID的方法
- 诛仙手游android转苹果,诛仙手游安卓和iOS互通吗 安卓和iOS能不能一起玩
- 高斯定理的证明(三重积分的C/C++实现)(C++)(大学物理)
- html 广告加载页面,JS广告代码延迟加载或是最后加载加快页面载入
- python中counter怎么用_带你走进python 计数器类Counter的用法
- Linux /etc/shadow(影子文件)内容解析(超详细)
- Google SketchUp Cookbook: (Chapter 1) Making Multiple Copies