算法学习笔记(15): Splay树
1.算法学习笔记(1):CDQ分治2.算法学习笔记(2):分块3.算法学习笔记(3):莫队算法4.算法学习笔记(4):FHQ平衡树(无旋平衡树)5.算法学习笔记(5):AC自动机6.算法学习笔记(6):优秀trick和性质合集7.算法学习笔记(7):数论8.算法学习笔记(9):第k大问题合集9.算法学习笔记(10):各种序的美好性质10.算法学习笔记(11):历史版本和线段树11.算法学习笔记(12):左偏树12.算法学习笔记(13):同余最短路13.算法学习笔记(14):区间最值操作和历史最值问题
14.算法学习笔记(15): Splay树
15.算法学习笔记(16):Link Cut Tree16.算法学习笔记(17):Slope trick17.算法学习笔记(18):珂朵莉树18.算法学习笔记(20):网络流19.算法学习笔记(21):数论分块20.算法学习笔记(22):莫比乌斯反演21.算法学习笔记(23):杜教筛22.算法学习笔记(24):筛法23.暑假集训学习笔记(1):lxl DS Day 124.暑假集训学习笔记(2):lxl DS Day 225.暑假集训学习笔记(3):lxl DS Day 326.多项式笔记27.生成函数笔记28.插头DP29.DP选讲做题记录 by 付乙淼30.拓展摩尔投票31.图论知识总结Splay树
Splay树又名伸展树, 是tarjan为LCT而发明的平衡树, 通过旋转操作维护二叉搜索树的高度平衡, 其实不管时间复杂度的证明, Splay树挺简单的。 均摊复杂度
算法
splay树通常是把需要操作的点旋转到根, 这样就可以进行
rotate操作
分为左旋和右旋, 目的是将节点
splay操作
这是将某个节点旋转到根节点处的一系列操作, 我们将其统称为splay操作。 也就是说会经历若干个左旋和右旋操作, 从而将
splay操作需要分类讨论:
此处请见OI-wiki splay分类讨论 (太懒了)
之所以我们要这样分类讨论, 是因为遵循这些规则转的话可以使高度较为平衡, 接近于
模板代码
点击查看代码
#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;
}
区间操作
区间翻转
可以打一个懒标记, 至于怎么打, 就是把代表区间
方法: 将
模板代码
点击查看代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App