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;
}
标签:int,siz,pos,son,fa,平衡,now
From: https://www.cnblogs.com/zhone-lb/p/18522222