LCT 动态树模板
给 n n n 个点和每个点的权值,处理以下操作:
0 x y
查询x
到y
之间的点权的 x o r \mathrm{xor} xor 和1 x y
连接x,y
,若已经联通则不用再连接2 x y
删除x,y
,不保证边存在3 x y
将x
的点权变成y
参考:https://www.cnblogs.com/flashhu/p/8324551.html
LCT 用 Splay 动态维护一个森林,目的是用平衡树来表示不平衡树,使得所有操作的复杂度都是 O ( log n ) O(\log{n}) O(logn)
其核心操作是 access(x)
,作用是使 x
到其根节点上的路径成为一条实链。具体地:
void access(int x){
int y = 0; // 初始需要赋值的右孩子是 0 (None)
while(x){
splay(x); // 首先把 x splay 到顶端
// 一棵树被划分为多个 splay 此处的操作是在 splay 中的操作
// c[x][1] 表示 splay 中 x 的右孩子,
// splay 的中序遍历要求深度递增,
// 而现在 x 在 splay 顶端,其右孩子(若存在)
// 必定比 x 的深度要深,这样是不符合 LCT 中 splay 定义的
// 因此在第一次操作时,要将 x 的右孩子置空
// 并且注意:此处置空的是 splay 中的右孩子
// 而在原树中 x 的孩子的 path parent 仍然是 x
// 也就是说 f[c[x][1]]=x (置空前)
c[x][1]=y; // 对于第一次来说,此处是将 x 的右孩子置空
pushup(x); // 更新信息
// 此时 x 已经到当前 splay 的顶端
// 下一步是到当前 splay 的 path parent 那里去,继续向上延长实链
// f[x] 即是当前 splay 的 path parent
// 但是在原树中 f[x] 不一定是 x 的父亲
// 它是当前 splay 中深度最小的那个结点的父亲
// 也就是最靠左的那个结点的父亲
// 沿着 path parent 到了上一个结点(比如 w)之后
// 再重复执行 splay(w) ,即将 w 变成其 splay 中的顶端结点
// 并使得 w 的右孩子变成结点 x
// 此时便完成了实链的延申, w 所在的 splay 和 x 所在的 splay 也就接了起来
// 所以下句话 y = x 就是要存下当前的结点
// 用以上面 splay 的延申
// 依次重复,直到当前结点的 path parent 为 0
// 也就是说当前结点已经变成了根节点
// 此时该 spaly 的根节点为原树的根
// 最右侧结点,也就是深度最大的结点是 x (第一次的 x)
// 并且 x 没有右孩子(在 splay 中)
y=x; // 使下次操作的右孩子可以指向当前结点
x=f[x]; // 下一个点是当前的 path parent
}
}
makeroot(x)
使得 x
变成原树中的根,方法是首先 access(x)
,建立从根节点到 x
的 splay ,然后 splay(x)
,此时 x
在 splay 的顶端,由于 x
是该 splay 中最深的点,因此这时 x
是没有右子树的,所以考虑直接翻转 splay ,使得 splay 中所有结点的深度翻转,使得 x
成为深度最小的结点,也就变成了根节点。
void pushr(int x){
swap(c[x][0],c[x][1]); // 直接交换 x 的左右孩子
r[x]^=1; // 懒惰标记,剩下的子树的孩子们现在还不用翻转
}
void makeroot(int x){
access(x); // 首先建立根到 x 的实链
splay(x); // x 移到 splay 的顶端
pushr(x); // 翻转 splay ,使 x 变成根
}
findroot(x)
用来找 x
在原树中的根,首先 access(x)
,建立 x
到根的一条实链通路,此时 x
和原树的根就在一个 splay 里啦,然后再 splay(x)
,使得 x
位于 splay 的顶端,由于 splay 中深度从左到右是递增的,因此 x
必定没有右子树,根节点必定在 splay 的最左侧,所以要一直找左子树的左孩子。找到之后再 splay
使得根节点在 splay 的顶端。
void pushdown(int x){
if(r[x]){
if(lc)pushr(lc);
if(rc)pushr(rc);
r[x]=0;
}
}
int findroot(int x){
access(x); // 首先建立 x 到根之间的实链通路
splay(x); // 使 x 在 splay 的顶端
while(c[x][0]){ // 一直找左子树
pushdown(x); // 向下更新翻转信息
x=c[x][0];
}
splay(x);
return x;
}
split(x,y)
的作用是在 x
和 y
之间拉一条实链:
void split(int x,int y){
makeroot(x); // 将 x 作为原树的根
access(y); // 拉一条从根(x)到 y 的实链
splay(y); // 令 y 作为 splay 的顶端(splay 的根)
}
link(x,y)
是将 x
连到 y
上,即 y
作为 x
的父亲:
bool link(int x,int y){
makeroot(x); // 使 x 作为根节点
if(findroot(y)==x)return 0; // 已经在同一原树内,不再连;如果保证连边合法的话这句话可以删掉
f[x]=y; // x 的父亲为 y (其实是 path parent 是 y,也就是一条虚边)
return 1;
}
cut(x,y)
是将 x
和 y
的连边断开,保证断边合法时:
void cut(int x,int y){
split(x,y); // 首先xy之间拉一条实链,此时 y 是 splay 的根,x 是原树的根,在 splay 中 x 必定是 y 的左儿子,在原树中,x 必定是 y 的父亲(path parent)
f[y]=c[y][0]=0; // 断开 x 和 y 之间的边
pushup(y); // 更新维护的信息
}
一般的情况:
void cut(int x,int y){
makeroot(x);
if(findroot(y)==x&&f[y]==x&&!c[y][0]){
f[y]=c[x][1]=0;
pushup(x);
}
}
代码如下:
#include<iostream>
#include<cstdio>
#define MAXN 300009
#define lc c[x][0]
#define rc c[x][1]
using namespace std;
int n,m,t,x,y,f[MAXN],c[MAXN][2],v[MAXN],s[MAXN],st[MAXN];
bool r[MAXN];
bool nroot(int x){//whether x isn't the root of splay
return c[f[x]][0]==x||c[f[x]][1]==x;
}
void pushup(int x){
s[x]=s[lc]^s[rc]^v[x];
}
void pushr(int x){int t=lc;lc=rc;rc=t;r[x]^=1;}
void pushdown(int x){
if(r[x]){
if(lc)pushr(lc);
if(rc)pushr(rc);
r[x]=0;
}
}
void rotate(int x){
int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
if(nroot(y))c[z][c[z][1]==y]=x;c[x][!k]=y;c[y][k]=w;
if(w)f[w]=y;f[y]=x;f[x]=z;
pushup(y);
}
void splay(int x){
int y=x,z=0;
st[++z]=y;//stack, stores the whole path from cur point to root
while(nroot(y))st[++z]=y=f[y];
while(z)pushdown(st[z--]);
while(nroot(x)){
y=f[x];z=f[y];
if(nroot(y))
rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
rotate(x);
}
pushup(x);
}
void access(int x){
for(int y=0;x;x=f[y=x])
splay(x),rc=y,pushup(x);
}
void makeroot(int x){
access(x);splay(x);
pushr(x);
}
int findroot(int x){
access(x);splay(x);
while(lc)pushdown(x),x=lc;
splay(x);
return x;
}
void split(int x,int y){
makeroot(x);
access(y);splay(y);
}
void link(int x,int y){
makeroot(x);
if(findroot(y)!=x)f[x]=y;
}
void cut(int x,int y){
makeroot(x);
if(findroot(y)==x&&f[y]==x&&!c[y][0]){
f[y]=c[x][1]=0;
pushup(x);
}
}
int main(){
#ifdef WINE
freopen("data.in","r",stdin);
#endif
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&v[i]);
while(m--){
scanf("%d%d%d",&t,&x,&y);
if(t==0){split(x,y);printf("%d\n",s[y]);}
if(t==1)link(x,y);
if(t==2)cut(x,y);
if(t==3){splay(x);v[x]=y;}
}
return 0;
}