算法学习笔记(15): Splay树

news/2024/10/8 20:32:38

Splay树

Splay树又名伸展树, 是tarjan为LCT而发明的平衡树, 通过旋转操作维护二叉搜索树的高度平衡。 均摊复杂度 \(O(logb)\), 可以区间操作, 不能可持久化, 常数较大(大于FHQtreap), 但是可以 \(O(nlogn)\) 实现 LCT。(这是唯一比FHQtreap优秀的店...)

算法

splay树通常是把需要操作的点旋转到根, 这样就可以进行 \(O(1)\) 的操作, 同时旋转时要注意不能无脑转, 要兼顾高度平衡。

rotate操作

分为左旋和右旋, 目的是将节点 \(x\) 旋转到父亲 \(f\) 的位置。 (图例搬的OI-wiki)
image

splay操作

这是将某个节点旋转到根节点处的一系列操作, 我们将其统称为splay操作。 也就是说会经历若干个左旋和右旋操作, 从而将 \(x\) 旋转到根。
splay操作需要分类讨论:
此处请见OI-wiki splay分类讨论 (太懒了)
之所以我们要这样分类讨论, 是因为遵循这些规则转的话可以使高度较为平衡, 接近于 \(O(logn)\), 从而达到均摊复杂度 \(O(logn)\)

模板代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Splay{int rt, tot, fa[N], ch[N][2], val[N], cnt[N], siz[N];void maintain(int u) { siz[u] = siz[ch[u][0]] + siz[ch[u][1]] + cnt[u]; }bool get(int u) { return u == ch[fa[u]][1]; }void clear(int u) { ch[u][0] = ch[u][1] = fa[u] = val[u] = cnt[u] = siz[u] = 0; }void rotate(int u) {int f = fa[u], gf = fa[f], chk = get(u);ch[f][chk] = ch[u][chk ^ 1];if (ch[u][chk ^ 1]) fa[ch[u][chk ^ 1]] = f;ch[u][chk ^ 1] = f;fa[f] = u;fa[u] = gf;if (gf) ch[gf][f == ch[gf][1]] = u;maintain(u);maintain(f);} void splay(int u) {for (int f = fa[u]; f = fa[u], f; rotate(u)) if (fa[f]) rotate(get(u) == get(f) ? f : u);rt = u;}void ins(int k) {if (!rt) {val[++tot] = k, cnt[tot]++;rt = tot;maintain(rt);return;}int cur = rt, f = 0;while (1) {if (val[cur] == k) {cnt[cur]++;maintain(cur);maintain(f);splay(cur);break;}f = cur;cur = ch[cur][val[cur] < k];if (!cur) {val[++tot] = k;cnt[tot]++;fa[tot] = f;ch[f][val[f] < k] = tot;maintain(tot);maintain(f);splay(tot);break;}}}int rk(int k) {int res = 0, cur = rt;while (1) {if (k < val[cur]) cur = ch[cur][0];else {res += siz[ch[cur][0]];if (!cur) return res + 1;if (k == val[cur]) {splay(cur);return res + 1;}res += cnt[cur];cur = ch[cur][1];}}}int kth(int k) {int cur = rt;while (1) {if (ch[cur][0] && k <= siz[ch[cur][0]]) cur = ch[cur][0];else {k -= cnt[cur] + siz[ch[cur][0]];if (k <= 0) {splay(cur);return val[cur];	}cur = ch[cur][1];}}} int pre() {int cur = ch[rt][0];if (!cur) return cur;while (ch[cur][1]) cur = ch[cur][1];splay(cur);return cur;}int suf() {int cur = ch[rt][1];if (!cur) return cur;while (ch[cur][0]) cur = ch[cur][0];splay(cur);return cur;}void del(int k) {rk(k);if (cnt[rt] > 1) {cnt[rt]--;maintain(rt);return;}if (!ch[rt][0] && !ch[rt][1]) {clear(rt);rt = 0;return;}if (!ch[rt][0]) {int cur = rt;rt = ch[rt][1];fa[rt] = 0;clear(cur);return;}if (!ch[rt][1]) {int cur = rt;rt = ch[rt][0];fa[rt] = 0;clear(cur);return;}int cur = rt;int x = pre();fa[ch[cur][1]] = x;ch[x][1] = ch[cur][1];clear(cur);maintain(rt);}
}T;
int n;
int main() {scanf("%d", &n);for (int i = 1, op, x; i <= n; i++) {scanf("%d%d", &op, &x);switch(op) {case 1: T.ins(x); break;case 2: T.del(x); break;case 3: printf("%d\n", T.rk(x)); break;case 4: printf("%d\n", T.kth(x)); break;case 5: T.ins(x); printf("%d\n", T.val[T.pre()]); T.del(x); break;case 6: T.ins(x); printf("%d\n", T.val[T.suf()]); T.del(x); break;} }return 0;
}

区间操作

区间翻转

可以打一个懒标记, 至于怎么打, 就是把代表区间 \((l, r)\) 的子树提出来, 然后打标记。 类似FHQ的, 我们需要按照数组下标为BST的键值建树。
方法: 将 \(l - 1\) 旋转到根, 将 \(r + 1\) 旋转到 \(l - 1\) 的右儿子, 这样区间 \((l, r)\) 就都在 \(r + 1\) 的左子树里, 直接打标记就可以了。

模板代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int INF = 1e9;
int a[N];
struct Splay{int tot, rt, cnt[N], siz[N], val[N], ch[N][2], fa[N], tag[N];void maintain(int u) { siz[u] = siz[ch[u][0]] + siz[ch[u][1]] + cnt[u]; }void clear(int u) { cnt[u] = siz[u] = val[u] = ch[u][0] = ch[u][1] = fa[u] = tag[u] = 0; }bool get(int u) { return u == ch[fa[u]][1]; }void pushdown(int u) {if (u && tag[u]) {tag[ch[u][0]] ^= 1;tag[ch[u][1]] ^= 1;swap(ch[u][0], ch[u][1]);tag[u] = 0; }}  void build(int &p, int l, int r, int pre) {if (l > r) return;p = ++tot; int mid = l + r >> 1;cnt[tot]++, val[tot] = a[mid], fa[tot] = pre;build(ch[p][0], l, mid - 1, p);build(ch[p][1], mid + 1, r, p);maintain(p);}void rotate(int u) {int f = fa[u], gf = fa[f], chk = get(u);pushdown(f); pushdown(u);ch[f][chk] = ch[u][chk ^ 1];if (ch[u][chk ^ 1]) fa[ch[u][chk ^ 1]] = f;ch[u][chk ^ 1] = f;fa[f] = u;fa[u] = gf;if (gf) ch[gf][f == ch[gf][1]] = u;maintain(u);maintain(f);}void splay(int u, int goal) {for (int f = fa[u]; (f = fa[u]) != goal; rotate(u)) if (fa[f] != goal) rotate(get(f) == get(u) ? f : u);if (!goal) rt = u;}int kth(int k) {int cur = rt;while(1) {pushdown(cur);if (ch[cur][0] && k <= siz[ch[cur][0]]) cur = ch[cur][0];else  {k -= siz[ch[cur][0]] + cnt[cur];if (k <= 0) return cur;cur = ch[cur][1];}}}void reverse(int l, int r) {l = l - 1, r = r + 1;l = kth(l), r = kth(r);splay(l, 0); splay(r, l);tag[ch[r][0]] ^= 1;}void print(int u) {pushdown(u);if (ch[u][0]) print(ch[u][0]);if (val[u] != -INF && val[u] != INF) printf("%d ", val[u]);if (ch[u][1]) print(ch[u][1]);}
}T;
int n, m;
int main() {scanf("%d%d", &n, &m);a[1] = -INF, a[n + 2] = INF;for (int i = 2; i <= n + 1; i++) a[i] = i - 1;T.build(T.rt, 1, n + 2, 0); for (int i = 1, l, r; i <= m; i++) {scanf("%d%d", &l, &r);l++, r++;T.reverse(l, r);}T.print(T.rt);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/28048.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

JuiceFS v1.2-beta1,Gateway 升级,多用户场景权限管理更灵活

JuiceFS v1.2-beta1 今天正式发布。在这个版本中,除了进行了大量使用体验优化和 bug 修复外,新增三个特性:Gateway 功能扩展:新增了“身份和访问管理(Identity and Access Management,IAM)” 与 “事件通知” ,为用户提供更安全、灵活和自动化的数据管理和监控能力,适…

DP32RF002—低功耗SUB-1G收发一体SOC芯片

DP32RF002是基于ARM Cortex-M0+内核的超低功耗、高性能的、单片集成(G)FSK/OOK无线收发机的32位SoC芯片。工作于200~960MHz范围内,支持灵活可设的数据包格式,支持自动应答和自动重发功能,支持跳频操作,支持FEC功能,同时内部集成了完整的射频接收机、射频发射机、频率综合器…

盘点效率工具RunFlow那些容易被忽略的功能

本文我们将带您了解RunFlow有哪些容易被忽略、但是又非常实用的功能。还不了解RunFlow?从这里开始了解。固定工作窗口您还可以通过双击 Ctrl 键来切换窗口固定状态,您也可以在 热点事件 设置页面自定义该快捷键。预览菜单内容用浏览器打开剪贴板复制的URL多行输入按 Ctrl+Ent…

LSTM时间序列预测中的一个常见错误以及如何修正

当使用LSTM进行时间序列预测时,人们容易陷入一个常见的陷阱。为了解释这个问题,我们需要先回顾一下回归器和预测器是如何工作的。预测算法是这样处理时间序列的:一个回归问题是这样的:因为LSTM是一个回归量,我们需要把时间序列转换成一个回归问题。有许多方法可以做到这一点…

Plumed分子模拟后分析

Plumed是一个强大的分子模拟数据处理工具,可以在模拟的过程中逐步分析,也可以保存模拟的轨迹做后分析。本文紧接前面的“增强采样软件PLUMED的安装与使用”文章,还有“直方图与核密度估计”文章。介绍了如何使用Plumed后分析工具,对输出的反应坐标的轨迹进行核密度估计。技…

动手学深度学习——卷积操作

卷积 卷积概念卷积原属于信号处理中的一种运算,引入CNN中,作为从输入中提取特征的基本操作 补零:在输入端外侧填补0值使得卷积输出结果满足某种大小,在外侧的每一边都添加0值,使得输出可以达到某种预定形状 跨步:卷积核在输入上滑动时每次移动到下一步的距离使用张量实现…

MyBatis-Plus 分页查询配置

说明一下 ,使用MyBatis-Plus 进行分页查询时 ,要先进行配置添加配置 /*** @Author North* @Date 2024/5/6*/ @Configuration public class MPConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new My…

VS打包项目成.exe.msi

VS打包项目成.exe&.msi ref:https://blog.csdn.net/weixin_44790046/article/details/103016154准备工作VS 2022(VS2017无法安装Installer Projects扩展,未知原因) Installer Projects (扩展 > 管理扩展 > 联机 > 搜索 > Microsoft Visual Studio Installe…