学习笔记:Splay

之前学习 Treap 的时候理解的不是太好,堆性质和 BST 性质结合得不是很好。发现后续知识对于 Splay 是需要掌握的,于是心血来潮学习了 Splay,个人感觉理解的比 Treap 好,以后手写平衡树就用 Splay 啦。
模板题:Splay

开点

比较类似 Treap,只不过不需要随机啦。特殊需要记录的是每个节点的 \(fa\),这样方便在旋转和插入中使用。(\(ch[x][0]\) 表示左儿子,\(ch[x][1]\) 表示右儿子)

int create(int v,int fs)
{
	val[++tot] = v,fa[tot] = fs;
	ch[tot][0] = ch[tot][1] = 0;
	siz[tot] = cnt[tot] = 1;
	return tot;
}

旋转

和 Treap 的旋转类似,手动模拟。一共是三对关系(注:\(x\) 的父亲为 \(ff\)\(ff\) 的父亲为 \(gf\)\(getwhich\) 求节点的儿子方向):

  1. \(x\) 的异向儿子变为 \(ff\) 的与 \(x\) 同向的儿子。
  2. \(x\) 变为 \(gf\) 的与 \(ff\) 同向的儿子。
  3. \(ff\) 变为 \(x\) 的异向儿子。

注意更新时,既要更新 \(ch\) 数组,还要更新 \(fa\) 数组。最后记得更新节点信息。

void rotate(int x)
{
	int ff = fa[x],gf = fa[ff];
	int k = getwhich(x),tmp = ch[x][k ^ 1];
	fa[tmp] = ff,ch[ff][k] = tmp;
	fa[x] = gf;
	if(gf) ch[gf][getwhich(ff)] = x;
	fa[ff] = x,ch[x][k ^ 1] = ff;
	maintain(x),maintain(ff);
}

Splay

关键的操作,其作用就是把节点 \(x\) 通过旋转变成根。不断旋转即可,注意特殊情况是如果 \(x\)\(ff\) 的儿子方向同向,需要先转 \(ff\),达到消链的效果,请自行画图 qaq。

void splay(int x)
{
	for(int ff = fa[x];ff = fa[x];rotate(x)) 
	    if(fa[ff]) rotate(getwhich(ff) == getwhich(x) ? ff : x);
	root = x;
}

插入

如果是空树,那么新建节点并设为根。否则的话根据 BST 性质找到应该插入的地方新建节点,最后通过 Splay 操作把新建的节点转到根位置,把不要忘记更新节点信息。

void ins(int v)
{
	if(not root)
	{
		root = create(v,0);
		return ;
	}
	int cur = root,ff = 0;
	while(1)
	{
		if(val[cur] == v)
		{
			cnt[cur]++;
			maintain(cur),maintain(ff);
			splay(cur); break;
		}
		ff = cur;
		cur = ch[cur][val[cur] < v];
		if(not cur)
		{
			ch[ff][val[ff] < v] = create(v,ff);
			maintain(ff);
			splay(tot); break;
		}
	}
}

查询排名为 \(k\) 的值

类似于 Treap,从根开始寻找,利用 \(siz\)\(cnt\) 判断该排名是在左子树、当前节点还是右子树,如果是左右子树需向左右儿子移动。注意右子树需要把排名减掉左子树的节点数和当前节点的 \(cnt\),实现见代码,自行理解一下呀。

int bargain(int k)
{
	int cur = root;
	while(1)
	{
		if(k <= siz[ch[cur][0]]) cur = ch[cur][0];
		else if(siz[ch[cur][0]] + cnt[cur] >= k) return val[cur];
		else
		{
			k -= siz[ch[cur][0]] + cnt[cur]; 
			cur = ch[cur][1];
		} 
	}
}

查询值为 \(v\) 的排名

和上面的操作是类似的,自行理解。注意的是,最后为了方便删除,需要把该节点通过 Splay 旋转到根上。(因为这样能简化删除,直接删根就可以了)

int rankle(int v)
{
	int cur = root,ans = 0;
	while(1)
	{
		if(not cur) return ans + 1;
		if(v < val[cur]) cur = ch[cur][0];
		else if(v == val[cur])
		{
			ans += siz[ch[cur][0]];
			splay(cur);
			return ans + 1;
		}
		else 
		{
			ans += siz[ch[cur][0]] + cnt[cur];
			cur = ch[cur][1];
		}
	}
}

求前驱和后继

这两个操作是类似的,这里是直接对于根进项操作。我们通过将查询值插入,通过 ins 里面的 Splay 把它转到根上,最后再删掉它就可以啦。两个操作是相反的,不是很难理解。

int charge()
{
	int cur = ch[root][0];
	while(ch[cur][1]) cur = ch[cur][1];
	return cur;
}
int recoil()
{
	int cur = ch[root][1];
	while(ch[cur][0]) cur = ch[cur][0];
	return cur;
}

删除

这个好好讲讲。首先需要注意,这里我们删除的是值为 \(v\) 的节点,而不是编号(易错)。我们通过 rankle 找到这个值为 \(v\) 的节点并把它转到根上,这样我们删根就可以了。分情况讨论:

  1. 如果要删的节点在平衡树中有不止一个,直接减小删除的节点的 \(cnt\) 即可。
  2. 如果根既没有左儿子,又没有右儿子,那我们直接把树变成空树。(\(root=0\)
  3. 如果只没有左儿子,那么我们把根设为右儿子即可,整棵树变成了右子树。
  4. 同理,如果只没有右儿子,我们把根设为左儿子,整棵树变成了左子树。
  5. 最后一种情况,如果两个儿子都有,那么我们考虑,找到这个节点的前驱,通过 Splay 把它转到根上,同时提前记录原先根(即删除节点)的右儿子,令现在的根和这个记录的节点建立右向的父子关系即可。

注意更新节点信息。

void reliese(int v)
{
	rankle(v);
	if(cnt[root] > 1)
	{
		cnt[root]--; maintain(root);
		return ;
	}
	else if(not ch[root][0] and not ch[root][1])
	{
		root = 0;
		return ;
	}
	else if(not ch[root][0])
	{
		root = ch[root][1];
		fa[root] = 0;
		return ;
	}
	else if(not ch[root][1])
	{
		root = ch[root][0];
		fa[root] = 0;
		return ;
	}
	int tmp = ch[root][1],pre = charge();
	splay(pre);
	fa[tmp] = root;
	ch[root][1] = tmp;
	maintain(root);
}

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100000 + 10;
const int inf = 1 << 29;
int n,tot,root;
int val[N],fa[N],cnt[N],ch[N][2],siz[N];
int create(int v,int fs)
{
	val[++tot] = v,fa[tot] = fs;
	ch[tot][0] = ch[tot][1] = 0;
	siz[tot] = cnt[tot] = 1;
	return tot;
}
void maintain(int x)
{
	siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + cnt[x];
}
int getwhich(int x)
{
	return x == ch[fa[x]][1];
}
void rotate(int x)
{
	int ff = fa[x],gf = fa[ff];
	int k = getwhich(x),tmp = ch[x][k ^ 1];
	fa[tmp] = ff,ch[ff][k] = tmp;
	fa[x] = gf;
	if(gf) ch[gf][getwhich(ff)] = x;
	fa[ff] = x,ch[x][k ^ 1] = ff;
	maintain(x),maintain(ff);
}
void splay(int x)
{
	for(int ff = fa[x];ff = fa[x];rotate(x)) 
	    if(fa[ff]) rotate(getwhich(ff) == getwhich(x) ? ff : x);
	root = x;
}
void ins(int v)
{
	if(not root)
	{
		root = create(v,0);
		return ;
	}
	int cur = root,ff = 0;
	while(1)
	{
		if(val[cur] == v)
		{
			cnt[cur]++;
			maintain(cur),maintain(ff);
			splay(cur); break;
		}
		ff = cur;
		cur = ch[cur][val[cur] < v];
		if(not cur)
		{
			ch[ff][val[ff] < v] = create(v,ff);
			maintain(ff);
			splay(tot); break;
		}
	}
}
int bargain(int k)
{
	int cur = root;
	while(1)
	{
		if(k <= siz[ch[cur][0]]) cur = ch[cur][0];
		else if(siz[ch[cur][0]] + cnt[cur] >= k) return val[cur];
		else
		{
			k -= siz[ch[cur][0]] + cnt[cur]; 
			cur = ch[cur][1];
		} 
	}
}
int rankle(int v)
{
	int cur = root,ans = 0;
	while(1)
	{
		if(not cur) return ans + 1;
		if(v < val[cur]) cur = ch[cur][0];
		else if(v == val[cur])
		{
			ans += siz[ch[cur][0]];
			splay(cur);
			return ans + 1;
		}
		else 
		{
			ans += siz[ch[cur][0]] + cnt[cur];
			cur = ch[cur][1];
		}
	}
}
int charge()
{
	int cur = ch[root][0];
	while(ch[cur][1]) cur = ch[cur][1];
	return cur;
}
int recoil()
{
	int cur = ch[root][1];
	while(ch[cur][0]) cur = ch[cur][0];
	return cur;
}
void reliese(int v)
{
	rankle(v);
	if(cnt[root] > 1)
	{
		cnt[root]--; maintain(root);
		return ;
	}
	else if(not ch[root][0] and not ch[root][1])
	{
		root = 0;
		return ;
	}
	else if(not ch[root][0])
	{
		root = ch[root][1];
		fa[root] = 0;
		return ;
	}
	else if(not ch[root][1])
	{
		root = ch[root][0];
		fa[root] = 0;
		return ;
	}
	int tmp = ch[root][1],pre = charge();
	splay(pre);
	fa[tmp] = root;
	ch[root][1] = tmp;
	maintain(root);
}
signed main()
{
	scanf("%lld",&n);
	//build();
	for(int i = 1;i <= n;i++)
	{
		int opt,x;
		scanf("%lld%lld",&opt,&x);
		if(opt == 1) ins(x);
		else if(opt == 2) reliese(x);
		else if(opt == 3) printf("%lld\n",rankle(x));
		else if(opt == 4) printf("%lld\n",bargain(x));
		else if(opt == 5)
		{
			ins(x);
			printf("%lld\n",val[charge()]);
			reliese(x);
		}
		else if(opt == 6)
		{
			ins(x);
			printf("%lld\n",val[recoil()]); 
			reliese(x);
		}
	}
	return 0;
}
posted @ 2021-12-05 11:26  一程山雪  阅读(73)  评论(2)    收藏  举报