平衡树

Splay

技巧/记忆点:

1、Rotate()中,使用变量记录位置关系和下标;

2、Find()(找元素\(x\)所在位置)减少重复代码;

3、求前驱/后继时先把这个数插进去再删掉;

4、Splay()父子同侧先翻父亲,再翻儿子,否则翻两边儿子;

5、siz[]在Splay()前先Pushup()到树根,这样在Rotate()维护会轻松一点;

6、Delete()先旋删点至根,删掉,再把左子树的最大值旋至根,连右子树即可;

代码

#include<iostream>
#define dir(x) son[fa[x]][1]==x
using namespace std;
const int MAXN=2e5+100;
int n;
class Balance_Tree {
	private:
		int a[MAXN],fa[MAXN],son[MAXN][2],cnt[MAXN],siz[MAXN],rt,tot;
		void Pushup(int x) {
			for(;x;x=fa[x]) siz[x]=siz[son[x][0]]+siz[son[x][1]]+cnt[x];
		}
		void Rotate(int x) {
			bool dx=dir(x),dy=dir(fa[x]);
			int y=fa[x],z=fa[fa[x]];
			siz[y]=cnt[y]+siz[son[y][!dx]]+siz[son[x][!dx]];
			siz[x]=siz[x]+siz[son[y][!dx]]+cnt[y];
			fa[x]=z;
			son[z][dy]=x;
			fa[son[x][!dx]]=y;
			son[y][dx]=son[x][!dx];
			fa[y]=x;
			son[x][!dx]=y;
		}
		void Splay(int x,int goal) {
			for(;fa[x]!=goal;Rotate(x)) {
				if(fa[fa[x]]) {
					if(dir(x)==dir(fa[x])) Rotate(fa[x]);
					else Rotate(x);
				}
			}
			if(!goal) rt=x;
		}
	public:
		int Find(int x) {
			int pos=rt,pre=0;
			while(pos&&a[pos]!=x) 
				pre=pos,pos=son[pos][a[pos]<x];
			return (pos?pos:pre);
		}
		void Insert(int x) {
			if(!rt) {
				a[++tot]=x;
				cnt[tot]=siz[tot]=1;
				rt=tot;
				return;
			}
			int pos=Find(x);
			if(a[pos]==x) ++cnt[pos],++siz[pos]; 
			else {
				son[pos][a[pos]<x]=++tot;
				fa[tot]=pos;
				a[tot]=x;
				siz[tot]=cnt[tot]=1;
				pos=tot;
			}
			Pushup(pos);
			Splay(pos,0);
		}
		void Delete(int x) {
			int pos=Find(x);
			Splay(pos,0);
			--cnt[pos]; --siz[pos];
			if(!cnt[pos]) {
				fa[son[pos][0]]=fa[son[pos][1]]=0;
				if(son[pos][0]==0||son[pos][1]==0) {
					rt=son[pos][son[pos][0]==0];
					return;
				}
				rt=son[pos][0];
				Splay(Find(1e9),0);
				son[rt][1]=son[pos][1];
				fa[son[pos][1]]=rt;
				siz[rt]+=siz[son[pos][1]];
			}
		}
		int Getrank(int x) {
			Splay(Find(x),0);
			return siz[son[rt][0]]+1;
		}
		int Getk_th(int x) {
			int pos=rt;
			while(true) {
				if(siz[son[pos][0]]>=x) pos=son[pos][0];
				else if(siz[son[pos][0]]+cnt[pos]>=x) return a[pos];
				else x-=(siz[son[pos][0]]+cnt[pos]),pos=son[pos][1];
			}
		}
		int Getpre(int x) {
			Insert(x);
			int pos=rt,pre=0;
			while(pos) pre=pos,pos=son[pos][a[pos]<x];
			Delete(x);
			return a[pre];
		}
		int Getnxt(int x) {
			Insert(x);
			int pos=rt,pre=0;
			while(pos) pre=pos,pos=son[pos][a[pos]<=x];
			Delete(x);
			return a[pre];
		}
}tr;
inline int read() {
	int x=0,op=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') op=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=(x<<3)+(x<<1)+(ch^'0');
		ch=getchar();
	}
	return x*op;
}
int main() {
	n=read();
	for(int i=1;i<=n;i++) {
		int op=read(),x=read();
		if(op==1) tr.Insert(x);
		if(op==2) tr.Delete(x);
		if(op==3) cout<<tr.Getrank(x)<<endl;
		if(op==4) cout<<tr.Getk_th(x)<<endl;
		if(op==5) cout<<tr.Getpre(x)<<endl;
		if(op==6) cout<<tr.Getnxt(x)<<endl;
	}
	return 0;
} 

文艺平衡树

题目链接

维护区间翻转操作

即对于一棵树的每个节点,都交换左右儿子

只需把树转成这个样子,然后打标记即可

正确性

乍一看存在左右儿子交换,二叉搜索树的性质会被破坏,但是考虑到所有数加进来后,它们的值就用不到了

构造该结构:将第\(l-1\)个和第\(r+1\)个数转上来即可,用\(siz[]\)维护

因此用\(Getk\_th()\)即可,权值用不到,所以破坏了也不影响

还有一个问题,就是构造结构时有Splay操作,是否会因为标记而错误

这个是不会的,在\(Getk\_th()\)中已经完成了\(rt\to pos\)这条路径的Pushdown,而Splay恰就只在这条链上转,所以该链已经正确,至于别的点,无所谓,旋转不会对它们造成影响

核心代码

void Pushdown(int x) {
	if(tag[x]) {
		swap(son[x][0],son[x][1]);
		tag[son[x][0]]^=tag[x];
		tag[son[x][1]]^=tag[x];
		tag[x]=0;
	}
}
int Getk_th(int x) {
	int now=rt;
	while(true) {
		Pushdown(now);
		if(siz[son[now][0]]==x-1) return now;
		else if(siz[son[now][0]]>x-1) now=son[now][0];
		else x-=siz[now]-siz[son[now][1]],now=son[now][1];
	}
}
void Reverse(int l,int r) {
	Splay(Getk_th(l-1),0);
	Splay(Getk_th(r+1),rt);
	tag[son[son[rt][1]][0]]^=1;
}
posted @ 2024-11-02 17:16  Zhone_lb  阅读(15)  评论(0)    收藏  举报