摘要
动态树,一类用来维护森林连通性的数据结构,主要使用Splay来维护偏爱点/边(Preferred child/edge),并且通过点在不同Splay中的移动提取路径,或者是修改父子关系以连接或断开树边
动态树的构建
动态树由于其需要动态的修改点/边关系,所以需要动态的维护点/边,从而选择偏爱点/边而不是像树链剖分中使用轻重点/边,同时需要Spaly维护
每个Splay中保存着一条偏爱路径,从浅到深维护深度
因为在同一棵树中,路径是有交点的,不同的Splay通过这些交点联系起来
不想多说,看代码注释
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
#define L ch[x][0]
#define R ch[x][1]
struct LCT {
int ch[N][2] , f[N] , rev[N] , val[N] , x0r[N];
int sta[N];
bool get(int x) {return ch[f[x]][1] == x;}
void update(int x) {x0r[x] = x0r[L] ^ x0r[R] ^ val[x];}
void pushdown(int x) {if(rev[x]) {rev[x] ^= 1; rev[L] ^= 1; rev[R] ^= 1; swap(L , R);}}
bool isroot(int x) {return ch[f[x]][0] != x && ch[f[x]][1] != x;}
void rotate(int x) {
int old = f[x] , oldf = f[old] , w = get(x);
if(!isroot(old)) ch[oldf][ch[oldf][1] == old] = x;
ch[old][w] = ch[x][w ^ 1]; f[ch[old][w]] = old;
ch[x][w ^ 1] = old; f[old] = x;
f[x] = oldf;
update(old); update(x);
}
void splay(int x) {
//因为每次从下层splay取出一个点加入上层节点,这个点必然是深度最小节点,新根必然是深度次小节点,也就是提升上去节点的儿子
//对于非根节点来说,其f指splay中的f,而对于根节点来说,f不指splay中的f,而是指联系上层splay的节点
//每次splay其他节点就会将这个联系上层splay的f转移给新的根节点,但是最终要提升是会将深度最小节点作为根节点再进行提升,也就是直接提升为f的儿子
//所以可以说整个对于同一棵树形成的splay森林,其实就是用虚儿子强行隔开的一个完整的spaly
int fa = x , top = 0;
sta[++ top] = fa;
while(!isroot(fa)) {sta[++top] = fa = f[fa];}
while(top) pushdown(sta[top --]);
//从上向下传递所有标记,防止剩余
for(int fa ; !isroot(x) ; rotate(x)) {
if(!isroot(fa = f[x]))
rotate(get(x) == get(fa) ? fa : x);
}
}
void access(int x) {
//若x与其父亲不在一棵splay中,显然x为深度最小节点,那么将其旋至splay根,并且和其父亲建立联系,抛弃深度比它大的节点,其父亲肯定为深度最大节点,直接接在其右侧,成为上层splay的深度最深节点
//若x与其父亲在一棵splay中,那么抛弃掉比x深的节点,然后将其父亲旋转至splay根时必然相邻,不会抛弃其他节点
//最终深度最深节点仍然是x
//这也是因为其实只有第一次才抛弃了节点,其他时刻并没有抛弃节点
//创建出一条x-root的通路,x作为深度最深的节点应该在整棵splay的最右侧
for(int i = 0 ; x ; i = x , x = f[x]) {
splay(x);
R = i;
update(x);
}
}
void makeroot(int x) {
access(x);
splay(x);
rev[x] ^= 1;//最深节点变为最浅节点(根)
}
int find(int x) {
access(x);
splay(x);
while(ch[x][0]) x = ch[x][0];
return x;//寻找x这棵树的最浅节点(根)
}
void split(int x , int y) {//创建y->x(rt),并且将y旋转至根
makeroot(x);
access(y);
splay(y);
}
void cut(int x , int y) {
split(x , y);//显然x深度较小(根),如果有路径x<->y,那么说明dep[y] = dep[x] + 1,x,y两者中序遍历应该相邻,即ch[y][0] = x , ch[x][1] = 0;
if(ch[x][1] == 0 && ch[y][0] == x) {
ch[y][0] = 0;
f[x] = 0;
}
}
void link(int x , int y) {
//最浅节点挂上父亲,因为保证原来没有相连,所以本来根节点的f应该是0
makeroot(x);
f[x] = y;
}
}lct;
int read() {
int ans = 0 , flag = 1;
char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
while(ch >= '0' &&ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
return ans * flag;
}
int main() {
int n = read() , m = read();
for(int i = 1 ; i <= n ; ++ i) lct.val[i] = lct.x0r[i] = read();
while(m --) {
int opt = read();
switch (opt) {
case 0 : {
int x = read() , y = read();
lct.split(x , y);
printf("%d\n" , lct.x0r[y]);
break;
}
case 1 : {
int x = read() , fx = lct.find(x) , y = read() , fy = lct.find(y);
//如果没有删边操作尽量使用并查集维护
if(fx != fy) lct.link(x , y);
break;
}
case 2 : {
int x = read() , fx = lct.find(x) , y = read() , fy = lct.find(y);
if(fx == fy) lct.cut(x , y);
break;
}
case 3 : {
int x = read() , y = read();
lct.access(x); lct.splay(x);
lct.val[x] = y;
lct.update(x);
break;
}
}
}
return 0;
}