Splay 学习笔记

前言

咕咕咕咕咕
其实 Splay 是和 Treap 差不多时候学的,但是由于 某些原因 <- 模板没调出来 & 准备初赛没来得及写 所以一直咕咕咕到了现在。。。
那就步入正题吧。
前置知识:Treap的旋转

简要介绍

Splay 是一种平衡树,所以说显然它符合二叉搜索树(BST)的性质。
Splay 能干什么呢?它可以维护序列信息和区间信息。包括区间翻转,区间插入,区间删除以及一些区间操作,比如说区间加等等。

Splaying

Splay的核心操作就在于 splay (旋转),我们定义函数 splay(x,y) 代表把节点 x 变成节点 y 的儿子。
有了这个函数,在操作的时候我们只要把要操作的两个节点变成根节点和根节点的右儿子,这要我们只要对根节点的右儿子的左儿子为根的子树就可以了。如果是插入,就直接插进来,删除就只接删掉,否则就是打一个懒惰标记(类似于线段树的操作)就可以了。

接下来我们讲函数 splay(x,y) 该如何实现。
显然我们需要通过类似 Treap 的旋转来实现,我们只要把一个节点转上去就好了。但是我们发现,如果直接把这个节点向上转的话会影响平衡树的平衡,于是在 splay 的时候,我们每次需要两次 Zig/Zag 使该节点的深度减一,如果一个节点和它的父亲都是他们的父亲的左儿子或者是右儿子,那么就先转两次该节点,否则就先转这个节点的父亲再转这个节点 ,这样就可以保证这课树的平衡。大家可以手动模拟一下就可以体会为什么这样就可以让树更加平衡了。
时间复杂度显然是 \(O\left(\log n\right)\)
代码:

struct JTZ{
	int ch[2],p,v,siz,flag;
	// 分别代表:儿子,父亲,编号,字树的大小,懒惰标记(是否区间翻转)
	// ch[0]是左儿子,ch[1]是右儿子
}a[maxn];
void up(int p){ a[p].siz=a[a[p].ch[0]].siz+a[a[p].ch[1]].siz+1; return; }
void down(int p){
	if(a[p].flag){
		swap(a[p].ch[0],a[p].ch[1]);
		a[a[p].ch[0]].flag^=1;
		a[a[p].ch[1]].flag^=1;
	}
	a[p].flag=0; return;
}//下推标记
void rotate(int x){
	int y=a[x].p,z=a[y].p,k=a[y].ch[1]==x;
	a[z].ch[a[z].ch[1]==y]=x,a[x].p=z;
	a[y].ch[k]=a[x].ch[k^1],a[a[x].ch[k^1]].p=y;
	a[x].ch[k^1]=y,a[y].p=x; up(y); up(x); return;
}//rotate(x) 代表通过旋转让节点 x 的深度减一
void splay(int x,int k){
	register int y,z;
	while(a[x].p!=k){
		y=a[x].p; z=a[y].p;
		if(z!=k) if((a[y].ch[1]==x)!=(a[z].ch[1]==y)) rotate(x); else rotate(y);
		rotate(x);
	}
	if(!k) root=x; return;
}
posted @ 2021-09-12 16:11  jiangtaizhe001  阅读(48)  评论(0编辑  收藏  举报