Link-Cut-Tree学习笔记 Link-Cut-Tree学习笔记

原理:将树分成一条条链,之后用Splay维护

一.定义数组

int son[MAXN][2], fa[MAXN], sum[MAXN], val[MAXN], sta[MAXN], lazy[MAXN];

son记录每个节点的两个重儿子

fa记录每个节点的父亲

(fa和son双向为重边,单向为轻边)

sum为本题特有的数组,含义为子树异或和,val为节点权值,lazy为翻转标记

二.基础操作

1.merge
void merge(int x){
	sum[x] = sum[son[x][0]] ^ sum[son[x][1]] ^ val[x];
}

每当节点更新时要用一遍merge

2.spread
void spread(int x){
	if(lazy[x]){
        lazy[son[x][0]] ^= 1;
        lazy[son[x][1]] ^= 1;
        lazy[x] ^= 1, swap(son[x][0], son[x][1]);
	}
}

传标记的函数

3.isroot
bool isroot(int x){
	return son[fa[x]][0] != x && son[fa[x]][1] != x;
}

判断一个点是否为当前Splay块里的根

4.rotate
void rotate(int x){
	int y = fa[x], z = fa[y], id = son[y][1] == x;
	if(!isroot(y)) son[z][son[z][1]==y] = x;//和普通Splay不一样的地方,因为Splay的目的是旋转到当前Splay块的顶部
	fa[son[x][id^1]] = y, son[y][id] = son[x][id^1];
	fa[y] = x, son[x][id^1] = y, fa[x] = z;
	merge(y), merge(x);//注意不要先merge(x)了
}
5.splay
void splay(int x){
    //因为要保证lazy的传递,所以需要提前将这一条链上的点全部spread
	sta[0] = 0, sta[++sta[0]] = x;
	for(int i=x; !isroot(i); i=fa[i]) sta[++sta[0]] = fa[i];
	for(int i=sta[0]; i>=1; i--) spread(sta[i]);
	while(!isroot(x)){
		int y = fa[x], z = fa[y];
		if(!isroot(y)) (son[z][0] == y) ^ (son[y][0] == x) ? rotate(x) : rotate(y);
		rotate(x);
	}
}

三.普通操作

1.access
void access(int x){
	for(int y=0; x; y=x, x=fa[x])
        splay(x), son[x][1] = y, merge(x);
}

打通原树根到x的唯一路径(不多也不少)

2.find
int find(int x){
	access(x), splay(x);
	while(son[x][0]) x = son[x][0];
	return x;
}

找这个树的根(即打通路径后的Splay块中深度最小的节点)

3.makeroot
void makeroot(int x){
	access(x), splay(x), lazy[x] ^= 1;
}

换根操作,当splay(x)后,x为深度最大的点,所以无右节点,此时反转序列之后将变成深度最小的点。即新的数根。因为换根操作不会影响其他的Splay块的深度关系,所以x就是新的树根。

4.split
void split(int x,int y){
	makeroot(x), access(y), splay(y);
}

打通一条(x o y)的路径,并且打完之后顶部Splay块以y为根

5.link
void link(int x,int y){
	if(find(x) == find(y)) return;
	makeroot(x), fa[x] = y;
}

(x o y)之间连一条边,因为我们指定它连轻边,所以不用更新y的sum值

这里第3行不能写为

access(x), splay(x), fa[x] = y;

这等价于x所在Splay的根和y连边,而不是x

6.cut
void cut(int x,int y){
	split(x, y);
	if(son[y][0] == x && son[x][1] == 0) son[y][0] = fa[x] = 0;
	merge(y);
}

第3行保证了x和y连接(即x和y之间不存在其他深度的点)(注意要(son[x][1]==0)不然有可能x的右儿子还会有节点)

Luogu模板代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

inline long long read(){
	long long ans = 0, f = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9')
		f *= (ch == '-') ? -1 : 1, ch = getchar();
	do ans = ((ans << 1) + (ans << 3) + (ch ^ 48)), ch = getchar();
	while(ch >= '0' && ch <= '9');
	return ans * f;
}

const int MAXN = 3e5 + 5;

struct LinkCutTree{
	int son[MAXN][2], fa[MAXN], sum[MAXN], val[MAXN], sta[MAXN], lazy[MAXN];
	void merge(int x){
		sum[x] = sum[son[x][0]] ^ sum[son[x][1]] ^ val[x];
	}
	void spread(int x){
		if(lazy[x]) lazy[son[x][0]] ^= 1, lazy[son[x][1]] ^= 1, lazy[x] ^= 1, swap(son[x][0], son[x][1]);
	}
	bool isroot(int x){
		return son[fa[x]][0] != x && son[fa[x]][1] != x;
	}
	void rotate(int x){
		int y = fa[x], z = fa[y], id = son[y][1] == x;
		if(!isroot(y)) son[z][son[z][1]==y] = x;
		fa[son[x][id^1]] = y, son[y][id] = son[x][id^1];
		fa[y] = x, son[x][id^1] = y, fa[x] = z;
		merge(y), merge(x);
	}
	void splay(int x){
		sta[0] = 0, sta[++sta[0]] = x;
		for(int i=x; !isroot(i); i=fa[i]) sta[++sta[0]] = fa[i];
		for(int i=sta[0]; i>=1; i--) spread(sta[i]);
		while(!isroot(x)){
			int y = fa[x], z = fa[y];
			if(!isroot(y)) (son[z][0] == y) ^ (son[y][0] == x) ? rotate(x) : rotate(y);
			rotate(x);
		}
	}
	void access(int x){
		for(int y=0; x; y=x, x=fa[x]) splay(x), son[x][1] = y, merge(x);
	}
	void makeroot(int x){
		access(x), splay(x), lazy[x] ^= 1;
	}
	int find(int x){
		access(x), splay(x);
		while(son[x][0]) x = son[x][0];
		return x;
	}
	void split(int x,int y){
		makeroot(x), access(y), splay(y);
	}
	void cut(int x,int y){
		split(x, y);
		if(son[y][0] == x && son[x][1] == 0) son[y][0] = fa[x] = 0;
		merge(y);
	}
	void link(int x,int y){
		if(find(x) == find(y)) return;
		makeroot(x), fa[x] = y;
	}
}LCT;

int main(){
	int n = read(), m = read();
	for(int i=1; i<=n; i++) LCT.val[i] = LCT.sum[i] = read();
	while(m--){
		int op = read(), x = read(), y = read();
		if(op == 0) LCT.split(x, y), printf("%d
", LCT.sum[y]);
		else if(op == 1) LCT.link(x, y);
		else if(op == 2) LCT.cut(x, y);
		else if(op == 3) LCT.access(x), LCT.splay(x), LCT.val[x] = y, LCT.merge(x);
	}
    return 0;
}

结束了