坑了好久,本来打算就这样弃着,最近又被神秘力量驱使来学这个东西,过程很痛苦但最终还是知道它到底是啥了
它可以用来对森林进行各种操作,为了快速地实现各种操作,我们要像树剖一样把整个森林剖成许多条链,每条链都用splay维护
为了在splay中体现链中节点的相对顺序,splay以节点深度为关键字(从小到大)
注意:就是因为splay以深度为关键字,所以我们只可以把它当成序列数据结构来用,找第$k$大什么的都是不存在的
不同链之间的连接通过splay根节点的父亲来体现
如图中所示,假设链$a$的splay根节点为$p$,链$b$中的节点$x$与链$a$相连,那么我们让$fa_p=x$
把森林剖成这些链之后,我们需要对其进行各种操作,比如形态改变和路径信息查询
所有的操作都基于一个东西:access
access($x$),就是“访问”节点$x$
感性地理解,一般访问树中的节点都要从树根开始走,所以这个操作实际上就是把$x$到根路径上的点都弄到一棵splay里
实现不难,我们需要从$x$按照链一直往上跳,遇到非链的边就把它连到$x$所属的链上,并把涉及到的其他链适当断开
具体点,给一个真代码
void access(int x){
int y=0;
while(x){
splay(x);
ch[x][1]=y;
pushup(x);
y=x;
x=fa[x];
}
}
代码中的$y$表示当前链往下一条链的splay根节点,当执行splay($x$)后,$x$以及$x$的左子树都是$x$往上同一条链的点(因为深度$\leq dep_x$),把它连上之前的链(深度$\geq dep_x$),然后往上跳,一直到树根就ok了
如何判断一个点$u$是否为一棵splay的根?当$lson_{fa_u}\neq u$且$rson_{fa_u}\neq u$时,说明$u$是一棵splay的根节点(因为根节点的父亲指向另一条链的节点)
这样我们就完成了lct中的核心操作access,接下来用这个access随意乱搞就可以实现各种功能了
1.makeroot($x$):钦点$x$为树的根
void makeroot(int x){
access(x);
splay(x);
reverse(x);
}
假设原来的根为$r$,因为原来深度从小到大是$r\rightarrow x$,现在变为$x\rightarrow r$,所以深度的相对大小反了,所以需要把整棵splay区间翻转
2.link($x$,$y$):连接一条边$(x,y)$
void link(int x,int y){
makeroot(x);
fa[x]=y;
}
3.cut($x$,$y$):删除边$(x,y)$
void cut(int x,int y){
makeroot(x);
access(y);
splay(y);
fa[x]=0;
ch[y][0]=0;
pushup(y);
}
makeroot($x$)并access($y$)之后,这棵splay中就只有$x$和$y$两个节点了,断开就好
4.operate($x$,$y$):对$x\rightarrow y$这条路径做一些操作(询问,修改等)
void operate(int x,int y){
makeroot(x);
access(y);
splay(y);
//...
}
makeroot($x$)并access($y$)之后,这棵splay中只有$x\rightarrow y$路径上的点,我们可以对这棵splay做任何平衡树能做的操作
5.connected($x$,$y$):判断$x$和$y$是否在同一棵树中
bool connected(int x,int y){
if(x==y)return 1;
makeroot(x);
access(y);
splay(y);
return fa[x]!=0;
}
如果$x$和$y$在同一棵树上,那么在makeroot($x$)并access($y$)之后,$x$和$y$就在同一棵splay上了,再splay($y$),$x$就会被挤下去,即是说$fa_x\ne0$
刚接触的时候会觉得很不可理解,但认真去画一画,调一调就知道它的含义是啥了
另:修改单点时直接在splay上改是可以的
#include<stdio.h>
int fa[300010],ch[300010][2],v[300010],s[300010],r[300010];
#define ls ch[x][0]
#define rs ch[x][1]
void pushup(int x){s[x]=v[x]^s[ls]^s[rs];}
void swap(int&a,int&b){a^=b^=a^=b;}
void rev(int x){
r[x]^=1;
swap(ls,rs);
}
void pushdown(int x){
if(r[x]){
if(ls)rev(ls);
if(rs)rev(rs);
r[x]=0;
}
}
bool isrt(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
void gao(int x){
if(!isrt(x))gao(fa[x]);
pushdown(x);
}
void rot(int x){
int y,z,f,B;
y=fa[x];
z=fa[y];
f=ch[y][0]==x;
B=ch[x][f];
fa[x]=z;
fa[y]=x;
if(B)fa[B]=y;
ch[x][f]=y;
ch[y][f^1]=B;
if(ch[z][0]==y)ch[z][0]=x;
if(ch[z][1]==y)ch[z][1]=x;
pushup(y);
pushup(x);
}
void splay(int x){
int y,z;
gao(x);
while(!isrt(x)){
y=fa[x];
z=fa[y];
if(!isrt(y))rot((ch[y][0]==x&&ch[z][0]==y)||(ch[y][1]==x&&ch[z][1]==y)?y:x);
rot(x);
}
}
void access(int x){
int y=0;
while(x){
splay(x);
ch[x][1]=y;
pushup(x);
y=x;
x=fa[x];
}
}
void makert(int x){
access(x);
splay(x);
rev(x);
}
bool cn(int x,int y){
if(x==y)return 1;
makert(x);
access(y);
splay(y);
return fa[x]!=0;
}
void link(int x,int y){
if(cn(x,y))return;
makert(x);
fa[x]=y;
}
void cut(int x,int y){
makert(x);
access(y);
splay(y);
if(ch[y][0]!=x||ch[x][1]!=0)return;
fa[x]=0;
ch[y][0]=0;
pushup(y);
}
void modify(int x,int a){
splay(x);
v[x]=a;
pushup(x);
}
int query(int x,int y){
makert(x);
access(y);
splay(y);
return s[y];
}
int main(){
int n,m,i,x,y;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",v+i);
s[i]=v[i];
}
while(m--){
scanf("%d%d%d",&i,&x,&y);
if(i==0)printf("%d\n",query(x,y));
if(i==1)link(x,y);
if(i==2)cut(x,y);
if(i==3)modify(x,y);
}
}
555怎么才能让我的旋转变得短一点啊