ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

平衡树(Splay)

2022-07-02 13:09:50  阅读:258  来源: 互联网

标签:splay 宠物 cur val 领养 son Splay 平衡


平衡树

不同平衡树

有许多不同的平衡树

如:替罪羊树,AVI,红黑树,TreapFHQ-Treap (无旋Treap),SplaySBT

其中比较重点的是上述后四种

目前只学习了 SplayTreap

能够较为熟练的打出来的只有 Splay

有关 Splay

代码 (luoguP3396) :

#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

#define size six
#define next nex

const int N=2e5+5;

int n;
int size[N],son[N][2],f[N],cnt[N],val[N],tot,rt;

struct Splay{
	inline void maintain(int x){
		size[x]=size[son[x][0]]+size[son[x][1]]+cnt[x];
	}

	inline bool get(int x){
		return x==son[f[x]][1];
	}

	inline void rotate(int x){
		int y=f[x],z=f[y],k=get(x),w=son[x][k^1];
		son[y][k]=w,f[w]=y;
		son[z][get(y)]=x,f[x]=z;
		son[x][k^1]=y,f[y]=x;
		maintain(y),maintain(x);
	}
	
	inline void splay(int x,int pos){
		while(f[x]!=pos){
			int y=f[x],z=f[y];
			if(z!=pos){
				if(get(x)==get(y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
		if(pos==0) rt=x;
	}
	
	inline void insert(int x){
		int cur=rt,p=0;
		for(;cur&&val[cur]!=x;p=cur,cur=son[cur][x>val[cur]]);
		if(cur) ++cnt[cur];
		else{
			cur=++tot;
			if(p) son[p][x>val[p]]=cur;
			son[cur][0]=son[cur][1]=0;
			f[cur]=p,val[cur]=x;
			cnt[cur]=size[cur]=1;
		}
		splay(cur,0);
	}
	
	inline void find(int x){
		int cur=rt;
		for(;son[cur][x>val[cur]]&&x!=val[cur];cur=son[cur][x>val[cur]]);
		splay(cur,0);
	}
	
	inline int xth(int x){
		int cur=rt;
		while(1){
			if(son[cur][0]&&x<=size[son[cur][0]]) cur=son[cur][0];
			else if(x>size[son[cur][0]]+cnt[cur]){
				x-=size[son[cur][0]]+cnt[cur];
				cur=son[cur][1];
			}
			else{
				splay(cur,0);
				return cur;
			}
		}
	}
	
	inline int pre(int x){
		find(x);
		if(val[rt]<x) return rt;
		int cur=son[rt][0];
		while(son[cur][1]) cur=son[cur][1];
		return cur;
	}
	
	inline int next(int x){
		find(x);
		if(val[rt]>x) return rt;
		int cur=son[rt][1];
		while(son[cur][0]) cur=son[cur][0];
		return cur;
	}
	
	inline void del(int x){
		int pr=pre(x),suc=next(x);
		splay(pr,0),splay(suc,pr);
		int y=son[suc][0];
		if(cnt[y]>1){
			--cnt[y];
			splay(y,0);
		}
		else son[suc][0]=0;
		splay(suc,0);
	}
}t;
signed main(){
	n=read();
	t.insert(100000000);
	t.insert(-100000000);
	while(n--){
		int op=read(),x=read();
		if(op==1) t.insert(x);
		if(op==2) t.del(x);
		if(op==3){
			t.find(x);
			printf("%d\n",size[son[rt][0]]);
		}
		if(op==4) printf("%d\n",val[t.xth(x+1)]);
		if(op==5) printf("%d\n",val[t.pre(x)]);
		if(op==6) printf("%d\n",val[t.next(x)]);
	}
}

当然,之后可能会采用结构体的写法

Splay 是平衡树,它是通过 \(splay\) (伸展) 这一操作来维持平衡的

\(splay(x,pos)\) 表示通过不断 \(rotate\) 把 \(x\) 节点旋转到 \(pos\) 节点的儿子处

\(splay(x,0)\) 表示把 \(x\) 旋转到根

这样我们可以通过 \(splay(pre(val),0)\) \(splay(next(val),pre(val))\) 等一系列操作更方便的执行复杂的插入与修改

具体会在题目中阐述

Splay 有两点重要的地方:

  • 它可以维护序列
  • 它是 LCT(Link Cut Tree) 的基础

个人比较喜欢 Splay 的原因:好学,方便,比 Treap 运用的范围更广

当然 FHQ-Treap 貌似也能维护序列 (可我不会)

Splay 习题

P3391 文艺平衡树

题意

给你一个长度为 \(n\) 的序列,序列的第 \(i\) 项初始为 \(i\)

会进行 \(m\) 次翻转序列中区间 \([l,r]\) 的操作

最后输出序列

满足 \(1\leq n,m\leq 10^5\)

思路

将序列每个点的位置存入平衡树中 (这样它的中序遍历就是最后的序列)

上述操作可以 \(insert\) 也可以递归建树 (像线段树一样)

翻转区间 \([l,r]\) 时我们 \(splay(l-1,0)\) \(splay(r+1,l-1)\)

这样区间 \([l,r]\) 就在 \(r+1\) 的左子树中

然后将 \(r+1\) 的左子树中所有点的左右子树交换就行

但是一次全交换会 T

所以考虑像线段树一样整一个翻转的 \(lazytag\)

同时在每次访问前 \(pushdown\) 更新

这样就可以保证复杂度

code

P2234 营业额统计

题意

有 \(n\) 天,告诉你每天的营业额 \(a_i\)

我们定义,一天的最小波动值 = \(\min\{|\text{该天以前某一天的营业额}-\text{该天营业额}|\}\)

特别地,第一天的最小波动值为第一天的营业额

求最小波动值之和

思路

没什么特别,Splay

code

P1486 郁闷的出纳员

题意

给你 \(n\) 条命令,\(\min\) 的值 (工资下界)

  • I k 新建一个工资档案,初始工资为 \(k\)。如果某员工的初始工资低于工资下界,他将立刻离开公司。

  • A k 把每位员工的工资加上 \(k\) 。

  • S k 把每位员工的工资扣除 \(k\)。

  • F k 查询第 \(k\) 多的工资。

在初始时,可以认为公司里一个员工也没有。

  • \(0 \leq n \leq 3 \times 10^5\),\(0 \leq \text{min} \leq 10^9\)

思路

考虑到维护 A 操作比较复杂

搞一个增加值 \(delta\) 表示当前已经加过 \(delta\) 这么多工资

每次插入 \(x\) 的时候 \(insert(x-delta)\)

这样树内的每个值 \(val\) 实际上是 \(val+delta\)

由于需要删掉每个小于 \(min\) 的节点

每次删除操作时把大于等于 \(min-delta\) 的最小值 \(splay\) 到根,然后把根的左儿子变成 \(0\)

这样就完成了删除操作

注意这里调用 \(next(min-delta)\) 就要把 \(>\) 改成 \(\ge\),或者是调用 \(next(min-delta-1)\)

code

P2286 宠物收养场

题意

凡凡开了一间宠物收养场。收养场提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。

被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为 \(a\),那么它将会领养一只目前未被领养的宠物中特点值最接近 \(a\) 的一只宠物。

(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)

如果存在两只宠物他们的特点值分别为 \(a-b\) 和 \(a+b\),那么领养者将会领养特点值为 \(a-b\) 的那只宠物。

收养宠物的人过多,假若到来一只被收养的宠物,能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者。

如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为 \(a-b\) 和 \(a+b\),那么特点值为 \(a-b\) 的那个领养者将成功领养该宠物。

一个领养者领养了一个特点值为 \(a\) 的宠物,而它本身希望领养的宠物的特点值为 \(b\),那么这个领养者的不满意程度为 \(|a-b|\)。

给你 \(n\) 个 领养者和被收养宠物到来收养所的情况,请你计算所有收养了宠物的领养者的不满意程度的总和。

初始时,收养所里面既没有宠物,也没有领养者。

\(0<n\leq80000\)

思路

维护一棵 Splay 以及当前树内是宠物还是领养者

找前驱后继,若前驱后继都行则选前驱,然后删掉就行

code

P3850 书架

题意

有一个书架上有 \(N\) 本书,给你 \(M\) 本书以及要插入的位置 \(x\),将其插进去

\(Q\) 次询问,每次问你第 \(k\) 本书的名字

\(1 \leqslant N \leqslant 200\), \(1 \leqslant M \leqslant 10^5\), \(1 \leqslant Q \leqslant 10^4\)

思路

Splay 维护位置

每次插入到位置 \(x\) 就找到在树中排名为 \(x\) 的位置 \(a\) 并 \(splay(a,0)\)

找到在树中排名为 \(x-1\) 的位置 \(b\) 并 \(splay(b,a)\)

这样 \(b\) 的右儿子为空,我们让 \(x\) 为 \(b\) 的右儿子就完成了插入

每次查找直接问就行 (因为树中的排名就表示序列中的排名)

code

P3586 LOG

题意

维护一个长度为 \(n\) 的序列,一开始都是 \(0\),支持以下两种操作:

  1. U k a 将序列中第 \(k\) 个数修改为 \(a\)。
  2. Z c s 在这个序列上,每次选出 \(c\) 个正数,并将它们都减去 \(1\),询问能否进行 \(s\) 次操作。

每次询问独立,即每次询问不会对序列进行修改。

思路

操作 \(1\) 很容易

对于操作 \(2\) 分为大于等于 \(s\) 的数和小于 \(s\) 的数,让它们总贡献大于等于 \(c\cdot s\) 就行

假设大于等于 \(s\) 的数有 \(x\) 个,那么它们的贡献就是 \(x\cdot s\) (每一个数的贡献至多为 \(s\) )

而小于 \(s\) 的数的贡献就是它们的和 \(Sum\)

所以能进行 \(s\) 次操作的条件是: \(x\cdot s+Sum\ge c\cdot s\)

也就是 \(Sum\ge (c-x)\cdot s\)

因此在 Splay 中 \(maintain\) 的时候多维护一个东西 \(sum\) (这个东西的维护方式就和 \(size\) 一样)

设 \(pre(s)=y\),每次查询的时候 \(splay(y,0)\),那么就可以计算 \(Sum\) 和 \(x\) 了

\(Sum=sum[p]-sum[son[p][1]]\)

\(x=size[son[p][1]]]\)

注意:代码中 \(x=size[son[p][1]]-1\) 因为最开始插入了一个极大值防止越界

code

标签:splay,宠物,cur,val,领养,son,Splay,平衡
来源: https://www.cnblogs.com/into-qwq/p/16437085.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有