FHQ-Treap学习笔记
平衡树与FHQ-Treap
平衡树(即平衡二叉搜索树),是通过一系列玄学操作让二叉搜索树(BST)处于较平衡的状态,防止在某些数据下退化(BST在插入值单调时,树形不平衡,单次会退化成 \(\mathcal{O}(n)\) )。常见的平衡树有Treap、FHQ-Treap、Splay、AVL、红黑树等。平衡树应用广泛,可用于维护集合、数列,辅助LCT等。
FHQ-Treap是一种常数较小,码量较小,功能较多的平衡树。它通过分裂与合并操作,且在合并时根据节点的随机权值确定合并方案,来维持平衡。单次复杂度 \(\mathcal{O}(\log n)\)。
节点信息&基本操作
int s[N];//s[i]以i为根的子树大小
int r[N];//r[i]节点i的随机权值
int v[N];//v[i]节点i的权值
int ch[N][2];//ch[i][0]节点i的左儿子,ch[i][1]节点i的右儿子
int siz;//使用过的节点树,注意并不是树的大小
int rt;//根节点编号
int New(int val) {s[++siz] = 1; r[siz] = rand(); v[siz] = val; return siz;}//创建新节点,返回它的编号
void upd(int p) {s[p] = s[ch[p][0]] + s[ch[p][1]] + 1;}//更新子树大小
合并&分裂
merge(x, y)
将以x,y为根的两棵树合并为一棵,并返回合并后的根节点编号。以x为根的树中所有任意的权值不大于以y为根的树中所有任意的权值。
我们可以用递归的方式来实现。为了达到平衡,我们需要利用随机权值。通过比较x和y节点的随机权值来确定合并方案。
- x, y两棵树都非空时:
- x,y都为空时:直接返回0。
- x,y中一空一非空时:返回非空树的根节点,用位运算中的按位或实现。
注意以x为根的树中所有任意的权值时刻不大于以y为根的树中所有任意的权值。
int merge(int x, int y) {
if(!x || !y) return x | y;
return r[x] < r[y] ? (ch[x][1] = merge(ch[x][1], y), upd(x), x) : (ch[y][0] = merge(x, ch[y][0]), upd(y), y);
}
split(p, val, x, y)
将以p为根的树分裂为两棵树,根分别是x和y。其中x树中所有节点的权值小于等于val
,y树中所有节点的权值大于val
。
如果节点p的权值 \(\le val\),那么p和p的左子树的权值都 \(\le val\),它们一定属于x树。把p节点和p的左子树一并给x,然后在p的右子树内继续分裂。
反之,如果节点p的权值 \(>val\),那么p和p的右子树的权值都 \(>val\),它们一定属于y树。把p节点和p的右子树一并给y,然后在p的左子树内继续分裂。
void split(int p, int val, int &x, int &y) {
if(!p) {x = y = 0; return;}
v[p] <= val ? (x = p, split(ch[p][1], val, ch[p][1], y)) :
(y = p, split(ch[p][0], val, x, ch[p][0]));
upd(p);
}
split(p, sss, x, y)
将以p为根的树分裂为两棵树,根分别是x和y。其中x树为p树中序遍历的前sss个节点组成的树(按大小分裂)。
与按权值分裂的思想类似。当往右子树走时,要先减去左子树和根节点的大小(左子树大小+1),因为这个大小是相对以当前节点为根的子树而言的。
void split(int p, int sss, int &x, int &y) {
if(!p) {x = y = 0; return;}
if(sss > s[ch[p][0]]) x = p, split(ch[p][1], sss - s[ch[p][0]] - 1, ch[p][1], y);
else y = p, split(ch[p][0], sss, x, ch[p][0]);
upd(p);
}
插入
设插入的数为 \(val\) ,先按 \(val-1\)分裂出x和y,然后新建权值为 \(val\) 的节点p。依次合并x,p,y即可。
void insert(int val) {
int x, y;
split(rt, val - 1, x, y);
rt = merge(merge(x, New(val)), y);
}
在给定位置插入:把按权值分裂改成按大小分裂就行。
in void ins(int l, char val) {//在l位置后插入值为val的节点
int x, y;
split(rt, l, x, y);
rt = merge(merge(x, New(val)), y);
}
删除
设删除的数为 \(val\) ,先按 \(val\) 分裂出y和z,再将y按 \(val-1\) 分裂出x和y。如果y不为空,说明有值为 \(val\) 的数。如果只删除一个,那么将y设为y的左右儿子合并的结果,再依次合并x,y,z即可;如果删除所有值为 \(val\) 的数,直接合并x和z即可。
void erase(int val) {
int x, y, z;
split(rt, val, x, z);
split(x, val - 1, x, y);
if(y) y = merge(ch[y][0], ch[y][1]);
rt = merge(merge(x, y), z);
}
/*
void erase_all(int val) {
int x, y, z;
split(rt, val, x, z);
split(x, val - 1, x, y);
rt = merge(x, z);
}
*/
区间删除:分裂出目标树,合并其它树就得到了删除后的树。
void eraselr(int l, int r) {
int x, y, z;
split(rt, r, y, z);
split(y, l - 1, x, y);
rt = merge(x, z);
}
查找值的排名
排名定义为比它小的数的个数加1。分裂出权值比它小的树,答案就是这棵树的大小+1。
int rank(int val) {
int x, y, ans;
split(rt, val - 1, x, y);
ans = s[x] + 1;
rt = merge(x, y);
return ans;
}
根据排名查找值
从根节点出发,根据子树大小判断目标是否为当前节点,或要往哪里走。当往右子树走时,要先减去左子树和根节点的大小(左子树大小+1),因为这个排名是相对以当前节点为根的子树而言的。
int val(int rk) {
int p = rt;
while(p) {
if(s[ch[p][0]] + 1 == rk) return v[p];
else if(rk <= s[ch[p][0]]) p = ch[p][0];
else rk = rk - s[ch[p][0]] - 1, p = ch[p][1];
}
}
前驱&后继
前驱:小于 \(x\),且最大的数
后继:大于 \(x\),且最小的数
顺着这个思路,我们只要分裂出满足前半句话的树,然后就很容易在这棵树内得到满足后半句话的节点。
int prev(int val) {
int x, y, tmp;
split(rt, val - 1, x, y);
tmp = x;
while(ch[tmp][1]) tmp = ch[tmp][1];
rt = merge(x, y);
return v[tmp];
}
int next(int val) {
int x, y, tmp;
split(rt, val, x, y);
tmp = y;
while(ch[tmp][0]) tmp = ch[tmp][0];
rt = merge(x, y);
return v[tmp];
}
维护区间
FHQ-Treap也可以用于维护区间。如果我们让节点在真正序列中的位置满足BST性质,那么真正序列就是树的中序遍历。一般地,FHQ-Treap进行一次区间修改/查询的方法是:分裂出目标树 -> 修改/查询(一般使用类似线段树的懒标记) -> 合并。这里就要按大小分裂了。
与线段树不同的是,FHQ-Treap支持任意位置插入/删除、区间翻转等操作,但常数较大。
对于每个操作 \([l,r]\),我们先按大小分裂出这棵目标树。方法是,先分裂出 \(y=[1,r]\) 和 \(z=[r+1,n]\),再将y分裂出 \(x=[1,l-1]\) 和 \(y=[l,r]\)。此时的y就是目标树了。
然后,我们给y打上懒标记。注意分裂/合并时要下传标记。下传标记的方法视情况而定。
例题
所有操作前文已讲。
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define in __inline__
#define rei register int
char inputbuf[1 << 23], *p1 = inputbuf, *p2 = inputbuf;
#define getchar() (p1 == p2 && (p2 = (p1 = inputbuf) + fread(inputbuf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
in int read() {
register int res = 0;
char ch = getchar();
bool f = true;
for(; ch < '0' || ch > '9'; ch = getchar())
if(ch == '-') f = false;
for(; ch >= '0' && ch <= '9'; ch = getchar())
res = res * 10 + (ch ^ 48);
return f ? res : -res;
}
in void write(register int x){
static unsigned char _q[35]; register unsigned char t=0;
for(; x; x /= 10) _q[++t] = x % 10;
for(; t; --t) putchar(_q[t] + 48);
putchar(32);
}
in void swap(int &x, int &y) {x ^= y ^= x ^= y;}
const int N = 1e5 + 5;
int ch[N][2], s[N], r[N], v[N], siz, rt;
int New(int val) {s[++siz] = 1; r[siz] = rand(); v[siz] = val; return siz;}
void upd(int p) {s[p] = s[ch[p][0]] + s[ch[p][1]] + 1;}
int merge(int x, int y) {
if(!x || !y) return x | y;
if(r[x] > r[y]) {ch[x][1] = merge(ch[x][1], y); upd(x); return x;}
else {ch[y][0] = merge(x, ch[y][0]); upd(y); return y;}
}
void split(int p, int val, int &x, int &y) {
if(!p) {x = y = 0; return;}
v[p] <= val ? (x = p, split(ch[p][1], val, ch[p][1], y)) :
(y = p, split(ch[p][0], val, x, ch[p][0]));
upd(p);
}
in void insert(int val) {
int x, y;
split(rt, val - 1, x, y);
rt = merge(merge(x, New(val)), y);
}
in void erase(int val) {
int x, y, z;
split(rt, val, x, z);
split(x, val - 1, x, y);
if(y) y = merge(ch[y][0], ch[y][1]);
rt = merge(merge(x, y), z);
}
in int rank(int val) {
int x, y, ans;
split(rt, val - 1, x, y);
ans = s[x] + 1;
rt = merge(x, y);
return ans;
}
in int val(int rk) {
int p = rt;
while(p) {
if(s[ch[p][0]] + 1 == rk) return v[p];
else if(rk <= s[ch[p][0]]) p = ch[p][0];
else rk = rk - s[ch[p][0]] - 1, p = ch[p][1];
}
}
in int prev(int val) {
int x, y, tmp;
split(rt, val - 1, x, y);
tmp = x;
while(ch[tmp][1]) tmp = ch[tmp][1];
rt = merge(x, y);
return v[tmp];
}
in int next(int val) {
int x, y, tmp;
split(rt, val, x, y);
tmp = y;
while(ch[tmp][0]) tmp = ch[tmp][0];
rt = merge(x, y);
return v[tmp];
}
int main() {
int q = read(), opt, x, lst = 0, ans = 0;
srand(time(0));
for(; q; --q) {
opt = read(), x = read();
if(opt == 1) insert(x);
else if(opt == 2) erase(x);
else if(opt == 3) write(rank(x)));
else if(opt == 4) write(val(x));
else if(opt == 5) write(prev(x));
else write(next(x));
}
}
我们显然可以得到一个性质:同一个区间翻转偶数次等同于没有翻转。 于是我们可以利用异或运算来实现(\(0\operatorname{xor}\) 奇数次 \(1\) 得到 \(1\),偶数次得到 \(0\))。
暴力翻转整棵目标树就是交换每个节点的左右儿子,那么下传标记时更新左右儿子的标记,并交换左右儿子即可。
最终答案就是整棵树中序遍历得到的序列。递归输出时也要下传标记。
const int N = 1e5 + 5;
int s[N], ch[N][2], r[N], v[N], siz, rt, n, m, t[N];
in void push(int p) {
if(!t[p]) return;
swap(ch[p][0], ch[p][1]);
if(ch[p][0]) t[ch[p][0]] ^= 1;
if(ch[p][1]) t[ch[p][1]] ^= 1;
t[p] = 0;
}
in int New(int val) {
v[++siz] = val; s[siz] = 1; r[siz] = rand(); return siz;
}
in void upd(int p) {s[p] = s[ch[p][0]] + s[ch[p][1]] + 1;}
void print(int p) {
if(!p) return;
push(p);
print(ch[p][0]);
write(v[p]);
print(ch[p][1]);
}
int merge(int x, int y) {
if(!x || !y) return x | y;
return r[x] < r[y] ? (push(x), ch[x][1] = merge(ch[x][1], y), upd(x), x) : (push(y), ch[y][0] = merge(x, ch[y][0]), upd(y), y);
}
void split(int p, int sss, int &x, int &y) {
if(!p) {x = y = 0; return;}
push(p);
if(sss > s[ch[p][0]]) x = p, split(ch[p][1], sss - s[ch[p][0]] - 1, ch[p][1], y);
else y = p, split(ch[p][0], sss, x, ch[p][0]);
upd(p);
}
void rotate() {
int l = read(), r = read(), x, y, z;
split(rt, r, y, z);
split(y, l - 1, x, y);
t[y] ^= 1;
rt = merge(merge(x, y), z);
}
int main() {
srand(time(0));
n = read(); m = read();
for(rei i = 1; i <= n; ++i) rt = merge(rt, New(i));
for(; m; --m) rotate();
print(rt);
return 0;
}
有什么问题珂以在评论区提出并吊打这个蒟蒻/kk
FHQ-Treap学习笔记的更多相关文章
- fhq treap 学习笔记
序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- 左偏树 / 非旋转treap学习笔记
背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...
- treap学习笔记
treap是个很神奇的数据结构. 给你一个问题,你可以解决它吗? 这个问题需要treap这个数据结构. 众所周知,二叉查找树的查找效率低的原因是不平衡,而我们又不希望用各种奇奇怪怪的旋转来使它平衡,那 ...
- fhq treap抄袭笔记
目录 碎碎念 点一下 注意!!! 模板 fhq treap 碎碎念 我咋感觉合并这么像左偏树呢 ps:难道你们的treap都是小头堆的吗 fhq真的是神人 现在看以前学的splay是有点恶心,尤其是压 ...
- Treap + 无旋转Treap 学习笔记
普通的Treap模板 今天自己实现成功 /* * @Author: chenkexing * @Date: 2019-08-02 20:30:39 * @Last Modified by: chenk ...
- [Treap][学习笔记]
平衡树 平衡树就是一种可以在log的时间复杂度内完成数据的插入,删除,查找第k大,查询排名,查询前驱后继以及其他许多操作的数据结构. Treap treap是一种比较好写,常数比较小,可以实现平衡树基 ...
- Treap-平衡树学习笔记
平衡树-Treap学习笔记 最近刚学了Treap 发现这种数据结构真的是--妙啊妙啊~~ 咳咳.... 所以发一发博客,也是为了加深蒟蒻自己的理解 顺便帮助一下各位小伙伴们 切入正题 Treap的结构 ...
- 「FHQ Treap」学习笔记
话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...
- 「学习笔记」 FHQ Treap
FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...
随机推荐
- 关于C primer plus 的学习计划(暂停)
最近想学数据结构,但是C的基础不够好,想借着C primer plus补一下基础.怎料第一章看的还挺快,到第二章看了二十多天.现在改改阅读方式:每日在这里添加进度,然后精看例题习题和章总结,其它简略看 ...
- Pandas切片操作:很容易忽视的SettingWithCopyWarning
Pandas是一个强大的分析结构化数据的工具集,主要用于数据挖掘和数据分析,同时也提供数据清洗功能. 很多初学者在数据的选取,修改和切片时经常面临一些困惑.这是因为Pandas提供了太多方法可以做同样 ...
- python安装pycrypto库
使用pycharm时安装pycrypto库,一直安装不上,提示安装成功,退出去一看,依旧没有 最后选择了pip安装,但一直报错(Microsoft Visual C++ 9.0 is required ...
- SpringBoot:整合Shiro
目录 1.Shiro简介 1.1.什么是Shiro? 1.2.有哪些功能 1.3.Shiro架构(外部) 1.4.Shiro架构(内部) 2.HelloWorld 3.Shiro整合Spring Bo ...
- 王颖奇 201771010129《面向对象程序设计(java)》第七周学习总结
实验七 继承附加实验 实验时间 2018-10-11 1.实验目的与要求 (1)进一步理解4个成员访问权限修饰符的用途: A.仅对本类可见-private B.对所有类可见-public C.对本包和 ...
- [coj 1353 Guessing the Number]kmp,字符串最小表示法
题意:给一个字符串,求它的最小子串,使得原串是通过它重复得到的字符串的一个子串. 思路:先求最小长度,最小循环长度可以利用kmp的next数组快速得到,求出长度后然后利用字符串最小表示法求循环节的最小 ...
- P2220 [HAOI2012]容易题(快速幂)
Describe 为了使得大家高兴,小Q特意出个自认为的简单题(easy)来满足大家,这道简单题是描述如下: 有一个数列A已知对于所有的A[i]都是1~n的自然数,并且知道对于一些A[i]不能取哪些值 ...
- python循环中对一个列表的赋值问题
参考:https://www.cnblogs.com/zf-blog/p/10613981.html https://www.cnblogs.com/andywenzhi/p/7453374.html ...
- mysql查询日期分组,不存在的补全0,做每天数据图表,根据日期,N天数往前查
SELECT IFNULL( DATA.count, 0 ) AS count, day_list.DAY AS createTime FROM ( SELECT DATE_FORMAT( creat ...
- 简单的Java实现Netty进行通信
使用Java搭建一个简单的Netty通信例子 看过dubbo源码的同学应该都清楚,使用dubbo协议的底层通信是使用的netty进行交互,而最近看了dubbo的Netty部分后,自己写了个简单的Net ...