代码适中、非常灵活的平衡树。

需要前置:二叉搜索树。

一些基础的函数:

int idx, ch[N][2], cnt[N], sz[N], fa[N];
/*
idx 是节点计数, ch[i][0 / 1] 是 i 节点的左右子树节点
cnt[i] 是 i 节点的数量
sz[i] 是 i 节点子树的大小
fa[i] 是 i 的父亲
*/ // pushup
void inline pushup(int p) {
sz[p] = sz[ch[p][0]] + cnt[p] + sz[ch[p][1]];
} // 判断 p 是 fa[p] 左儿子还是右儿子 (0 / 1)
bool inline get(int p) {
return p == ch[fa[p]][1];
} // 清空一个节点
void inline clear(int p) {
ch[p][0] = ch[p][1] = val[p] = cnt[p] = sz[p] = fa[p] = 0;
}

\(\text{Pushup}\) 要放在旋转的最后。

\(\text{Pushdown}\) 只要递归就推下去。

旋转的意义:保持中序遍历不变,调整树高。

这样旋转后,在改变树形结构的基础上发现中序遍历保持不变。

void inline rotate(int x) {
int y = fa[x], z = fa[y], k = get(x);
ch[y][k] = ch[x][!k], fa[ch[x][!k]] = y;
ch[x][!k] = y, fa[y] = x;
fa[x] = z;
if (z) ch[z][y == ch[z][1]] = x;
pushup(y); pushup(x);
}

以下所有介绍的操作都是 Splay 的独特的操作,剩下的二叉搜索树就有了。

复杂度的保持 & 核心思想:

每次操作完的点,均将这个点旋转(Splay)到树根。

感性理解的好处:每一次用到,后面还有可能再用到。

严谨的证明,结论是若操作 \(m\) 次,总复杂度是 \(O(m \log n)\),平均意义每次操作都是 \(O(\log)\) 的。

Splay 翻转

定义函数 \(splay(x, k)\) 表示将点 \(x\) 旋转至 \(k\) 下面。

\(y = fa_x, z = fa_y\)。

迭代:

  • 如果 \(z\) 不存在,转一次 \(x\) 即可。
  • 若 \(z, y, x\) 是直线,那么先把 \(y\) 转上去,然后转 \(x\)
  • 否则是折线,就转两次 \(x\)

只有这么转复杂度才是对的,不能随便转,要背一下)

void inline splay(int p) {
for (int f = fa[p]; f = fa[p]; rotate(p))
if (fa[f]) rotate(get(p) == get(f) ? f : p);
rt = p;
}

以下标为键:将一段序列插入到 y 的后面

  • 找到 \(y\) 的后继 \(z\)
  • 将 \(y\) 旋转到根 \(splay(y, 0)\)
  • 将 \(z\) 转到 \(y\) 的下面 \(splay(z, y)\)

这样 \(z\) 一定没有左子树,直接把一段序列构造好的树节点赋值成 \(z\) 的左子树就行了。

以下标为键:操作一段

删除序列的 \([l, r]\)

\(splay(kth(l - 1), 0), splay(kth(r+1), l - 1)\),这样 \([l, r]\) 之间所有的点组成了以 \(r + 1\) 的左子树,这样直接就可以在 \(kth(r + 1)\) 的左儿子这个节点打 \(tag\) 就行了。

板子

P3369 【模板】普通平衡树

#include <cstdio>
#include <iostream> using namespace std; const int N = 100005; int n, m, rt;
int idx, ch[N][2], val[N], cnt[N], sz[N], fa[N]; void inline update(int p) {
sz[p] = sz[ch[p][0]] + cnt[p] + sz[ch[p][1]];
} bool inline get(int p) {
return p == ch[fa[p]][1];
} void inline clear(int p) {
ch[p][0] = ch[p][1] = val[p] = cnt[p] = sz[p] = fa[p] = 0;
} void inline rotate(int x) {
int y = fa[x], z = fa[y], k = get(x);
ch[y][k] = ch[x][!k], fa[ch[x][!k]] = y;
ch[x][!k] = y, fa[y] = x;
fa[x] = z;
if (z) ch[z][y == ch[z][1]] = x;
update(y); update(x);
} void inline splay(int p) {
for (int f = fa[p]; f = fa[p]; rotate(p))
if (fa[f]) rotate(get(p) == get(f) ? f : p);
rt = p;
} void insert(int &p, int x, int f) {
if (!p) {
p = ++idx, sz[p] = cnt[p] = 1, fa[p] = f, val[p] = x;
if (f) ch[f][x > val[f]] = p, update(f), splay(p);
} else if (val[p] == x) cnt[p]++, sz[p]++, update(f), splay(p);
else insert(ch[p][x > val[p]], x, p);
} int kth(int p, int k) {
if (k <= sz[ch[p][0]]) return kth(ch[p][0], k);
else if (k <= sz[ch[p][0]] + cnt[p]) { splay(p); return val[p]; }
else return kth(ch[p][1], k - sz[ch[p][0]] - cnt[p]);
} int getRank(int p, int k) {
int res = 0;
if (k < val[p]) return getRank(ch[p][0], k);
else if (k == val[p]) { res = sz[ch[p][0]] + 1; splay(p); return res; }
else { res += sz[ch[p][0]] + cnt[p]; return res + getRank(ch[p][1], k); }
} int inline pre() {
int p = ch[rt][0];
while (ch[p][1]) p = ch[p][1];
splay(p);
return p;
} int inline nxt() {
int p = ch[rt][1];
while (ch[p][0]) p = ch[p][0];
splay(p);
return p;
} void inline del(int k) {
getRank(rt, k);
if (cnt[rt] > 1) cnt[rt]--, sz[rt]--;
else if (!ch[rt][0] && !ch[rt][1]) {
clear(rt), rt = 0;
} else if (!ch[rt][0]) fa[rt = ch[rt][1]] = 0;
else if (!ch[rt][1]) fa[rt = ch[rt][0]] = 0;
else {
int p = rt, x = pre();
splay(x); ch[x][1] = ch[p][1], fa[ch[x][1]] = x;
clear(p); update(rt);
}
} int main() {
scanf("%d", &m);
while (m--) {
int opt, x; scanf("%d%d", &opt, &x);
if (opt == 1) {
insert(rt, x, 0);
} else if (opt == 2) {
del(x);
} else if (opt == 3) {
insert(rt, x, 0);
printf("%d\n", getRank(rt, x));
del(x);
} else if (opt == 4) {
printf("%d\n", kth(rt, x));
} else if (opt == 5) {
insert(rt, x, 0);
printf("%d\n", val[pre()]);
del(x);
} else if (opt == 6) {
insert(rt, x, 0);
printf("%d\n", val[nxt()]);
del(x);
}
}
}

P3391 【模板】文艺平衡树

#include <iostream>
#include <cstdio>
#define ls ch[p][0]
#define rs ch[p][1]
#define get(x) x == ch[fa[x]][1]
using namespace std; const int N = 100005; int n, m, val[N], ch[N][2], sz[N], fa[N], rev[N], rt, idx; void inline pushup(int p) {
sz[p] = sz[ls] + sz[rs] + 1;
} void inline reverse(int p) {
swap(ls, rs), rev[p] ^= 1;
} void inline pushdown(int p) {
if (rev[p]) {
if (ls) reverse(ls);
if (rs) reverse(rs);
rev[p] = 0;
}
} void inline rotate(int x) {
int y = fa[x], z = fa[y], k = get(x);
ch[y][k] = ch[x][!k], fa[ch[x][!k]] = y;
ch[x][!k] = y, fa[y] = x;
fa[x] = z;
if (z) ch[z][y == ch[z][1]] = x;
pushup(y), pushup(x);
} void inline splay(int x, int k) {
for (int f = fa[x]; (f = fa[x]) != k; rotate(x)) {
if (fa[f]) rotate(get(x) == get(f) ? f : x);
}
if (!k) rt = x;
} void build(int &p, int l, int r, int f) {
if (l > r) return;
p = ++idx;
int mid = (l + r) >> 1; val[p] = mid, fa[p] = f;
if (l < r) {
build(ch[p][0], l, mid - 1, p);
build(ch[p][1], mid + 1, r, p);
}
pushup(p);
} void print(int p) {
if (!p) return;
pushdown(p);
print(ch[p][0]);
if (val[p] && val[p] <= n) printf("%d ", val[p]);
print(ch[p][1]);
} int inline kth(int p, int k) {
pushdown(p);
if (k <= sz[ch[p][0]]) return kth(ch[p][0], k);
else if (k == sz[ch[p][0]] + 1) {
splay(p, 0);
return p;
} else return kth(ch[p][1], k - sz[ch[p][0]] - 1);
} int main() {
scanf("%d%d", &n, &m);
build(rt, 0, n + 1, 0);
while (m--) {
int l, r; scanf("%d%d", &l, &r);
int x = kth(rt, l), y = kth(rt, r + 2);
splay(x, 0); splay(y, x);
reverse(ch[y][0]);
}
print(rt);
return 0;
}

学习笔记:Splay的更多相关文章

  1. [学习笔记] Splay Tree 从入门到放弃

    前几天由于出行计划没有更博QwQ (其实是因为调试死活调不出来了TAT我好菜啊) 伸展树 伸展树(英语:Splay Tree)是一种二叉查找树,它能在O(log n)内完成插入.查找和删除操作.它是由 ...

  2. [学习笔记]Splay

    其实就是一道题占坑啦 [NOI2005]维护数列 分析: 每次操作都要 \(Splay\) 一下 \(Insert\) 操作:重建一棵平衡树,把 \(l\) 变成根,\(l+2\) 变成右子树的根,那 ...

  3. 平衡树splay学习笔记#2

    讲一下另外的所有操作(指的是普通平衡树中的其他操作) 前一篇的学习笔记连接:[传送门],结尾会带上完整的代码. 操作1,pushup操作 之前学习过线段树,都知道子节点的信息需要更新到父亲节点上. 因 ...

  4. [学习笔记]平衡树(Splay)——旋转的灵魂舞蹈家

    1.简介 首先要知道什么是二叉查找树. 这是一棵二叉树,每个节点最多有一个左儿子,一个右儿子. 它能支持查找功能. 具体来说,每个儿子有一个权值,保证一个节点的左儿子权值小于这个节点,右儿子权值大于这 ...

  5. 平衡树学习笔记(3)-------Splay

    Splay 上一篇:平衡树学习笔记(2)-------Treap Splay是一个实用而且灵活性很强的平衡树 效率上也比较客观,但是一定要一次性写对 debug可能不是那么容易 Splay作为平衡树, ...

  6. BST,Splay平衡树学习笔记

    BST,Splay平衡树学习笔记 1.二叉查找树BST BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值. 2.BST的用处 ...

  7. 学习笔记 | CDQ分治

    目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...

  8. OI知识点|NOIP考点|省选考点|教程与学习笔记合集

    点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...

  9. 平衡树学习笔记(6)-------RBT

    RBT 上一篇:平衡树学习笔记(5)-------SBT RBT是...是一棵恐怖的树 有多恐怖? 平衡树中最快的♂ 不到200ms的优势,连权值线段树都无法匹敌 但是,通过大量百度,发现RBT的代码 ...

  10. 平衡树学习笔记(5)-------SBT

    SBT 上一篇:平衡树学习笔记(4)-------替罪羊树 所谓SBT,就是Size Balanced Tree 它的速度很快,完全碾爆Treap,Splay等平衡树,而且代码简洁易懂 尤其是插入节点 ...

随机推荐

  1. tcpack--3快速确认模式- ack状态发送&清除

    ACK发送状态的转换图 ACK的发送状态清除 当成功发送ACK时,会删除延迟确认定时器,同时清零ACK的发送状态标志icsk->icsk_ack.pending ACK发送事件主要做了:更新快速 ...

  2. 这 12 款 IDEA 插件你用过几款?

    搞 Java开发用什么软件,当然是神器idea了,那么,idea的插件对于你来说就是必不可少的了,不仅可以提高自己的编码效率,还可以减轻工作时的枯燥烦闷.接下来就来说说,我平时敲代码用的什么插件吧. ...

  3. JUC锁种类总结

    在并发编程中有各种各样的锁,有的锁对象一个就身兼多种锁身份,所以初学者常常对这些锁造成混淆,所以这里来总结一下这些锁的特点和实现. 乐观锁.悲观锁 悲观锁 悲观锁是最常见的锁,我们常说的加锁指的也就是 ...

  4. SpringBoot加载配置文件的几种方式

    首先回忆一下在没有使用SpringBoot之前也就是传统的spring项目中是如何读取配置文件,通过I/O流读取指定路径的配置文件,然后再去获取指定的配置信息. 传统项目读取配置方式 读取xml配置文 ...

  5. mysql密码问题

    这位老哥的: 版权声明:本文为CSDN博主「csdn-华仔」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/ ...

  6. 一文带你读懂!华为云在ACMUG技术沙龙上都透露了些啥?

    摘要:近日,华为云数据库业务总裁苏光牛在ACMUG中国MySQL用户组主办的 "华为云专场" 技术沙龙中分享了华为云数据库重磅新品GaussDB的核心能力与竞争优势.那么, Gau ...

  7. 两年经验拿到蚂蚁金服,字节offer,附上金九银十BAT面试核心知识点整理

    前言 我自己是本科毕业后在老东家干了两年多,老东家算是一家"小公司"(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身),毕业这两年多我也没有在大厂待过,因此找 ...

  8. 标准库之time,random,sys,os

    # import time # print(time.time()) # 时间戳 # print(time.mktime(time.localtime())) # 结构化时间转换为时间戳 # prin ...

  9. TA-Lib技术指标分析

    import talib as tb from talib import * print(tb.get_functions()) print(tb.get_function_groups()) 指标大 ...

  10. 带你入门Camtasia Studio录像机软件

    Camtasia软件和其他录制软件不同,不论是编辑功能还是制作功能还是其他功能方面都远远高于其他录制软件.那这边我们可以一起了解一下基础软件功能. 首先,我们在电脑端安装了软件以后,进行实际操作.在操 ...