Splay 学习笔记

前言

  • msjing在sbmqwm的压力下去学了 \(Splay\),现在她学成(?)归来了

介绍

  • \(Splay\),又称伸展树。是依据 \(Splay\) 操作保持平衡的平衡树,操作是单 \(\log_n\)但好像常数也是起飞的,不会退化成链,仍然依据 \(BST\) 来维护

实现和维护

维护的变量

  • fa:父亲
  • ch[0] & ch[1]:左右儿子
  • val:权值
  • cnt:权值出现次数
  • siz:所在子树大小

\(Splay\)

  • \(Splay\) 操作是最关键的部分,由于 \(Splay\) 操作才能保持平衡,在这之前,我们先说旋转操作

旋转

  • 旋转操作是很多平衡树维持平衡的方法,非常难理解,msjing完全搞不懂呢,sbmqwm你够了别搞a~
  • 和一位
  • 旋转还要依据 \(BST\),保证中序遍历有序,总共有两种操作:左旋和右旋
  • 继续偷图
    批注 2026-07-10 152437
  • 我们来说一下是如何操作的,以右旋为例
  • 把 2 提上去,1 放下来,但是我们发现 2 有三个儿子,这不行,我们可以依据性质把 5 给到 1,就符合了,可以手动跑一下中序遍历
  • 具体怎么搞,e,msjing并不是挺会,给代码吧
void Rotate(int x)
{
	int y=st[x].fa,z=st[y].fa,k=st[y].ch[1] == x;
	st[z].ch[st[z].ch[1] == y]=x;
	st[x].fa=z;
	st[y].ch[k]=st[x].ch[k^1];
	st[st[x].ch[k^1]].fa=y;
	st[x].ch[k^1]=y;
	st[y].fa=x;
	Pushup(y),Pushup(x);
}
  • 等一下我先把sbmqwm赶出去
  • 那个Pushup,就是非常普通算 \(siz\)

\(Splay\)

  • 接下来就是最重要的操作了,分 3 步:\(zig\)\(zig\)-\(zig\)\(zig\)-\(zag\)
\(zig\)
  • \(zig\) 操作,指操作根节点,直接旋
    批注 2026-07-10 153653
  • 依旧偷图
\(zig\)-\(zig\)
  • \(zig\)-\(zig\) 操作,即不是操作的根节点,且操作点都是左儿子,e,说不清,看图就行,需要两次操作,先把 \(y\) 提上来,再提 \(x\),相当于利用 \(x\) 的父亲提 \(x\)
    批注 2026-07-10 153715
\(zig\)-\(zag\)
  • \(zig\)-\(zag\) 操作,还是操作的节点不是根节点,但是不是上述的都是左儿子,而是有左有右,呈折线状
    批注 2026-07-10 153727
  • 三个操作就讲完了,为什么只讲左儿子捏,因为右边是对称的
  • 这个东西为什么是对的,msjing没学过势能分析,搞不了,e,去oiwiki看看吧,msjing真不会证,反正这么做就是对的
void Splay(int x,int k)
{
	while (st[x].fa!=k)
	{
		int y=st[x].fa,z=st[y].fa;
		if (z!=k) (st[z].ch[0] == y)^(st[y].ch[0] == x) ? Rotate(x) : Rotate(y);
		Rotate(x);
	}
	if (k == 0) root=x;
}

其他操作

  • 好多就和 \(FHQ\) 很像了,可以到这里看看
  • e,给代码吧,msjing累了,咕咕咕
void fid(int x)
{
	int u=root;
	if (!u) return;
	while (st[u].ch[x>st[u].val] && x!=st[u].val) u=st[u].ch[x>st[u].val];
	Splay(u,0);
}
void Insert(int x)
{
	int u=root,fa=0;
	while (u && st[u].val!=x) fa=u,u=st[u].ch[x>st[u].val];
	if (u) st[u].cnt++;
	else
	{
		u=++tot;
		if (fa) st[fa].ch[x>st[fa].val]=u;
		st[u].ch[0]=st[u].ch[1]=0;
		st[tot].fa=fa;st[tot].val=x;
		st[tot].cnt=1;st[tot].siz=1;
	}
	Splay(u,0);
}
int nxt(int x,int f)
{
	fid(x);
	int u=root;
	if ((st[u].val>x && f) || (st[u].val<x &&!f)) return u;
	u=st[u].ch[f];
	while (st[u].ch[f^1]) u=st[u].ch[f^1];
	return u;
}
void Delete(int x)
{
	int lt=nxt(x,0),net=nxt(x,1);
	Splay(lt,0),Splay(net,lt);
	int del=st[net].ch[0];
	if (st[del].cnt>1) st[del].cnt--,Splay(del,0);
	else st[net].ch[0]=0;
}
int kth(int x)
{
	int u=root;
	if (st[u].siz<x) return 0;
	while (1)
	{
		int y=st[u].ch[0];
		if (x>st[y].siz+st[u].cnt) x-=st[y].siz+st[u].cnt,u=st[u].ch[1];
		else
		{
			if (st[y].siz>=x) u=y;
			else return st[u].val;
		}
	}
}
int rak(int x)
{
	Insert(x);
	fid(x);
	int k=st[st[root].ch[0]].siz;
	Delete(x);
	return k;
}
  • 操作都要再 \(Splay\) 一下保持平衡

总代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
constexpr int p=100003,maxn=2e5+10;
long long read() {long long x=0,f=1;char ch=getchar();while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}return x*f;}
int n;
namespace splay
{
	struct _ {int fa,cnt,ch[2],val,siz;}st[maxn];
	int tot,root;
	void Pushup(int x) {st[x].siz=st[st[x].ch[0]].siz+st[st[x].ch[1]].siz+st[x].cnt;}
	void Rotate(int x)
	{
		int y=st[x].fa,z=st[y].fa,k=st[y].ch[1] == x;
		st[z].ch[st[z].ch[1] == y]=x;
		st[x].fa=z;
		st[y].ch[k]=st[x].ch[k^1];
		st[st[x].ch[k^1]].fa=y;
		st[x].ch[k^1]=y;
		st[y].fa=x;
		Pushup(y),Pushup(x);
	}
	void Splay(int x,int k)
	{
		while (st[x].fa!=k)
		{
			int y=st[x].fa,z=st[y].fa;
			if (z!=k) (st[z].ch[0] == y)^(st[y].ch[0] == x) ? Rotate(x) : Rotate(y);
			Rotate(x);
		}
		if (k == 0) root=x;
	}
	void fid(int x)
	{
		int u=root;
		if (!u) return;
		while (st[u].ch[x>st[u].val] && x!=st[u].val) u=st[u].ch[x>st[u].val];
		Splay(u,0);
	}
	void Insert(int x)
	{
		int u=root,fa=0;
		while (u && st[u].val!=x) fa=u,u=st[u].ch[x>st[u].val];
		if (u) st[u].cnt++;
		else
		{
			u=++tot;
			if (fa) st[fa].ch[x>st[fa].val]=u;
			st[u].ch[0]=st[u].ch[1]=0;
			st[tot].fa=fa;st[tot].val=x;
			st[tot].cnt=1;st[tot].siz=1;
		}
		Splay(u,0);
	}
	int nxt(int x,int f)
	{
		fid(x);
		int u=root;
		if ((st[u].val>x && f) || (st[u].val<x &&!f)) return u;
		u=st[u].ch[f];
		while (st[u].ch[f^1]) u=st[u].ch[f^1];
		return u;
	}
	void Delete(int x)
	{
		int lt=nxt(x,0),net=nxt(x,1);
		Splay(lt,0),Splay(net,lt);
		int del=st[net].ch[0];
		if (st[del].cnt>1) st[del].cnt--,Splay(del,0);
		else st[net].ch[0]=0;
	}
	int kth(int x)
	{
		int u=root;
		if (st[u].siz<x) return 0;
		while (1)
		{
			int y=st[u].ch[0];
			if (x>st[y].siz+st[u].cnt) x-=st[y].siz+st[u].cnt,u=st[u].ch[1];
			else
			{
				if (st[y].siz>=x) u=y;
				else return st[u].val;
			}
		}
	}
	int rak(int x)
	{
		Insert(x);
		fid(x);
		int k=st[st[root].ch[0]].siz;
		Delete(x);
		return k;
	}
}
int main()
{
	n=read();
	splay::Insert(1e9),splay::Insert(-1e9);
	while (n--)
	{
		int op=read(),x=read();
		if (op == 1) splay::Insert(x);
		if (op == 2) splay::Delete(x);
		if (op == 3) printf("%d\n",splay::rak(x));
		if (op == 4) printf("%d\n",splay::kth(x+1));
		if (op == 5) printf("%d\n",splay::st[splay::nxt(x,0)].val);
		if (op == 6) printf("%d\n",splay::st[splay::nxt(x,1)].val);
	}
	return 0;
}

后话

  • \(Splay\) 这个msjing学的不是很清,主要会用 \(FHQ\) 就行,学 \(Splay\) 其实是为了动态树,以后有时间再补吧,咕咕咕
  • sbmqwm:宝你是鸽子吗
posted @ 2026-07-10 15:56  msjing  阅读(27)  评论(2)    收藏  举报