Splay 笔记

本文原在 2024-07-15 15:27 发布于本人洛谷博客。

一、基本概念

1. 二叉搜索树

一棵每个节点的左子树的权值都比那个节点要小,右子树的权值都比那个节点要大的二叉树。

二、Splay 的操作

1. 建点

需要统计这个点的权值 \(val\),子树大小 \(sz\),这个点的权值的“个数” \(cnt\),左儿子、右儿子、父节点编号 \(ls,rs,fa\)

struct node {
	int val, sz, cnt, ls, rs, fa;
} tree[N];
int rt, gid;
int newnode(int val) {
	tree[++gid] = {val, 1, 1, 0, 0, 0};
	return gid;
}

2. 初始化

插入两个哨兵节点 \(-10^9\)\(10^9\) 防止操作越界。

void init_() {
	gid = 0;
	int p = newnode(oo);
	rt = newnode(-oo);
	rs(rt) = p;
	fa(p) = rt;
	tree[rt].sz = 2;
}

3. 维护子树 \(sz\) 大小

就是线段树的 push_up。

void push_up(int x) {
	tree[x].sz = tree[x].cnt;
	if (ls(x) != 0) tree[x].sz += tree[ls(x)].sz;
	if (rs(x) != 0) tree[x].sz += tree[rs(x)].sz;
}

4. 旋转

以左旋(zag,右旋称为 zig)\(x\) 为例:

  • 断边,旋转只与他的父节点有关。

  • 向左旋转,也就是把 \(x\) 向左拿上来,\(y\) 向左放下去

  • 这时 \(x\) 有三棵子树,\(y\) 只有一棵。由二叉搜索树的定义,\(y<B<x\),所以把 \(B\) 放到 \(y\) 的右子树。

右旋同理。

void zag(int x) {
	int y = fa(x), z = fa(y);
	rs(y) = ls(x);
	if (ls(x) != 0) fa(ls(x)) = y;
	push_up(y);
	ls(x) = y;
	fa(y) = x;
	push_up(x);
	fa(x) = z;
	if (z != 0) {
		if (ls(z) == y) ls(z) = x;
		else rs(z) = x;
	}
}
void zig(int x) {
	int y = fa(x), z = fa(y);
	ls(y) = rs(x);
	if (rs(x) != 0) fa(rs(x)) = y;
	push_up(y);
	rs(x) = y;
	fa(y) = x;
	push_up(x);
	fa(x) = z;
	if (z != 0) {
		if (ls(z) == y) ls(z) = x;
		else rs(z) = x;
	}
}

5. \(Splay(x, T)\)

表示将 \(s\) 旋转至 \(T\) 的下方。

每次旋转只有以下六种情况。

(1). 一步到位:\(s\)\(T\) 的儿子。

左儿子就 \(zig(s)\),右儿子就 \(zag(s)\)

以左儿子为例:

(2). 在路上:\(s\)\(p\) 的儿子,\(p\)\(q\) 的儿子,\(s,p\)\(p,q\) 同侧。

同为左侧就 \(zig(p)\to zig(s)\),同为右侧就 \(zag(p)\to zag(s)\)

以同为左侧为例:

(3). (2) 的情况,但是 \(s,p\)\(p,q\) 异侧。

\(s\)\(p\) 左侧,\(p\)\(q\) 右侧就 \(zig(s)\to zag(s)\);反之就 \(zag(s)\to zig(s)\)

\(s\)\(p\) 左侧为例:

多次使用以上操作,我们就可以完成 \(Splay\) 操作。

void splay(int x, int T) {
	while(fa(x) != T) {
		int y = fa(x), z = fa(y);
		if (z == T) {
			if (x == ls(y)) zig(x);
			else zag(x);
		} else {
			bool xl = (x == ls(y)), yl = (y == ls(z));
			if (xl and yl) zig(y), zig(x);
			else if (!xl and !yl) zag(y), zag(x);
			else if (xl and !yl) zig(x), zag(x);
			else zag(x), zig(x);
		}
	}
	if (fa(x) == 0) rt = x;
}

6. 求前驱/后继

指小于(或大于)指定数 \(val\) 的最大(或最小)数。

以前驱为例。我们直接从根遍历二叉树,如果当前所在节点小于 \(val\),那么就记它为最优答案,遍历它的右子树,否则就不更新最优答案,遍历它的左子树。

int pre(int p, int val, int best) {
	if (p == 0) return best;
	if (val > tree[p].val) return pre(rs(p), val, p);
	else return pre(ls(p), val, best);
}
int nxt(int p, int val, int best) {
	if (p == 0) return best;
	if (val < tree[p].val) return nxt(ls(p), val, p);
	else return nxt(rs(p), val, best);
}

7. 插入操作

将当前插入数 \(val\) 的前驱 \(prev\) 旋转到整棵树的根,后继 \(next\) 旋转到根 \(prev\) 的右子树,那么 \(A\) 的这个地方绝对是空的(或者是 \(val\) 本身)。

简单证明:

  • \(prev\) 是比 \(val\) 小的最大数,所以比 \(val\) 小的其他数都比 \(prev\) 小,位于 \(B\) 的这个地方。

  • \(next\) 是比 \(val\) 大的最小数,所以比 \(val\) 大的其他数都比 \(next\) 大,位于 \(C\) 的这个地方。

  • 所以比 \(val\) 小或者比 \(val\) 大的任何数都不在 \(A\) 这个位置。

  • 证毕。

如果 \(next\) 的左子树为空,新增一个节点,否则就直接给那个节点的 \(cnt\)(统计这个节点有多少“个”)加一就行了。

int insert(int p, int val) {
	int prev = pre(rt, val, 0);
	splay(prev, 0);
	int next = nxt(rt, val, 0);
	splay(next, prev);
	if(ls(next) != 0) tree[ls(next)].cnt++, tree[ls(next)].sz++;
	else {
		ls(next) = newnode(val);
		fa(ls(next)) = next;
	}
	push_up(next);
	push_up(rt);
	return ls(next);
}

8. 删除

同插入理。

void remove(int p, int val) {
	if (p == 0) return;
	int prev = pre(rt, val, 0);
	splay(prev, 0);
	int next = nxt(rt, val, 0);
	splay(next, prev);
	if (ls(next) != 0 and tree[ls(next)].val == val) {
		if (tree[ls(next)].cnt > 1) tree[ls(next)].cnt--, push_up(ls(next));
		else ls(next) = 0;
		push_up(next);
		push_up(rt);
	}
}

9. 求当前数排名

从小到大的排名。

把他的前驱移到左子树,这样比他小的数都在它的左子树,所以就是左边整棵子树的大小加一(但由于哨兵的存在,最后调用要减回去一)。

int get_rank(int p, int val) {
	int prev = pre(rt, val, 0);
	if (prev == 0) return 1;
	splay(prev, 0);
	int next = nxt(rt, val, 0);
	splay(next, prev);
	return tree[ls(prev)].sz + tree[prev].cnt + 1;
}

10. 查找某一排名的树

看一下左边子树的大小有没有超过当前排名,超过了就往左子树找,还没达到就往右子树找。往右子树找要减去左边子树的大小。

int findk(int p, int k) {
	int lsz = 0;
	if (ls(p) != 0) lsz = tree[ls(p)].sz;
	if (k <= lsz) return findk(ls(p), k);
	else if (k <= lsz + tree[p].cnt) return p;
	else return findk(rs(p), k - lsz - tree[p].cnt);
}
posted @ 2025-02-11 16:01  Garbage_fish  阅读(9)  评论(0)    收藏  举报