LCT

性质

\(Link\) \(Cut\) \(Tree\) 是基于 \(Splay\) 与实链剖分实现的。

\(LCT\) 可以实现诸多操作:

  • 查询、修改链上的信息
  • 更换原树的根
  • 动态连边、删边
  • 动态维护连通性

\(LCT\) 在中序遍历的情况下总是深度严格单调递增的。每个节点仅包含于一个 \(Splay\) 当中。虚边连接两个 \(Splay\),原树中中序遍历靠前的 \(Splay\) 将会作为父节点;实边则连接 \(Splay\) 中的父亲与儿子节点。由虚边连接的两个点,可以从儿子指向父亲,但是父亲无法找到儿子。

如图中的一棵树中,\(Splay\) 结构可能是如下的:

每一个绿圈当中为一棵 \(Splay\)


函数

\(access\)

\(access(x)\) 即为把根节点与 \(x\) 节点放入一个 \(Splay\) 中,使两点由实边相连。

事实上,只需要四个操作即可完成:

  1. 将节点转到当前 \(Splay\) 的根
  2. 换儿子
  3. 更新信息
  4. 当前操作点转为轻边指向的父节点,在此重复 \(1\)\(3\) 操作。
inline void access(int x){
	for(register int y = 0; x; x = fa[y=x]){	//记录下原节点
		splay(x);								//x转到根节点 
		son[x][1] = y;							//x的右儿子指向原来的当前点 
		pushup(x);								//更新 
	}
}

\(makeroot\)

\(makeroot(x)\) 即将 \(x\) 转至根节点,且使左子树为空。

  1. \(access(x)\),在此之后 \(x\) 一定是 \(Splay\) 中最深的节点。

  2. \(Splay(x)\),无疑所有节点都会转到左子树上。

  3. \(pushreverse(x)\),交换左右子树,打上懒惰标记

inline void makeroot(int x){
	access(x);									//将x与根节点连通 
	splay(x);									//转动使x到根,此时右子树为空 
	pushreverse(x);								//交换左右子树 
}

\(findroot\)

\(findroot(x)\) 是查找 \(x\) 在当前 \(Splay\) 中真实的根节点。

  1. \(Splay\) 转换为没有右子树,\(x\) 为根节点。
  2. 搜索 \(x\) 的左儿子。此时因为 \(Splay\) 中序遍历左儿子深度总是更浅的,所以我们可以直接一直向下搜左儿子直到没有为止,最终的 \(x\) 即为根节点。

最后再 \(splay(x)\) 保证复杂度。

int findroot(int x){
	access(x);										//将x与根节点连通 
	splay(x);										//转动使x到根,此时右子树为空 
	while(son[x][0])	pushdown(x), x = son[x][0]; //向下搜索左子树直至没有 
	splay(x);										//保证时间复杂度 
	return x;
}

\(split\)

\(split(x, y)\) 可以创建 \(x\)\(y\) 之间的通路且记录下区间。

  1. 首先将 \(x\) 转至根
  2. 构建 \(x\)\(y\) 的通路
  3. \(y\)转至根节点,此时 \(sum[y]\) 中记录的就是答案
inline void split(int x, int y){
	makeroot(x);								//将x转至根 
	access(y);									//构建x到y的通路 
	splay(y);									//将y转至根 
}

\(link\)

\(link(x,y)\) 可判断在 \(x\)\(y\) 之间加边是否合法后加边

  1. \(x\) 转到根
  2. 然后判断 \(y\) 所在的 \(Splay\) 是否在此根节点是 \(x\),若不是则加边合法,\(fa[x]=y\)
inline void link(int x, int y){
	makeroot(x);								//将x转到根 
	if(findroot(y) != x)	fa[x] = y;			//若y不在这个splay中即可加边 
}

\(cut\)

\(cut(x,y)\) 可判断在 \(x\)\(y\) 之间删边是否合法后删边

  1. \(x\) 转至根节点
  2. 判断是否合法
    • \(y\) 在原树中父节点是 \(x\)
    • \(y\)\(Splay\) 中父节点是 \(x\)
    • \(y\) 的左儿子为空。因为 \(makeroot\) 以后 \(x\) 已经只有右子树了,若\(y\)含有左子树则意味着 \(y\) 的左子树会位于 \(x\)\(y\) 之间,\(x\)\(y\) 没有直接相连。
  3. \(y\) 的父亲节点指向 \(x\) 的右儿子节点
  4. 更新信息
inline void cut(int x, int y){
	makeroot(x);										//将x转到根 
	if(findroot(y) == x && fa[y] == x && !son[y][0]){	//若符合条件 
		fa[y] = son[x][1] = 0;							//y的父亲节点直接指向x的右子树 
		pushup(x);										//更新信息 
	}
}

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m;
int op, x, y;
int a[300005];
int fa[300005], son[300005][2], sumxor[300005], st[300005];
bool lazy[300005];

template<typename T>
inline void read(T&x){
    x = 0; char q; bool f = 1;
    while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
    while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
    x = f? x:-x;
}

template<typename T>
inline void write(T x){
    if(x < 0){
    	putchar('-');
		x = -x;	
    }
    if(x > 9)	write(x/10);
    putchar(x%10^48);
    return;
}

inline bool nroot(int x){										//判断x与fa[x]是否在同一splay中 
	return son[fa[x]][0] == x || son[fa[x]][1] == x;
}

inline void pushup(int x){
	sumxor[x] = sumxor[son[x][0]] ^ sumxor[son[x][1]] ^ a[x];	//向上更新sumxor值 
}

inline void pushreverse(int x){
	swap(son[x][0], son[x][1]);								//交换son[x][0]与son[x][1] 
	lazy[x] ^= 1;											//lazy更新 
}

inline void pushdown(int x){
	if(lazy[x]){
		if(son[x][0])	pushreverse(son[x][0]);				//如果son[x][0]存在则下放son[x][0] 
		if(son[x][1])	pushreverse(son[x][1]);				//如果son[x][1]存在则下放son[x][1] 
		lazy[x] = 0;										//标记为0 
	}
}

inline void rotate(int x){												//把x向上旋转 
	int y = fa[x], k = (son[y][1] == x), w = son[x][!k], z = fa[y];		//z是y的父节点,y是x的父节点,k表示x是y的左儿子还是右儿子,w是x的与y对x反向的儿子 
	if(nroot(y))	son[z][son[z][1] == y] = x;							//若y与z是否在一个splay中,z的y儿子变换为x 
	son[x][!k] = y;														//x与y的x儿子反向的那一边儿子为y 
	son[y][k] = w;														//y的与x同向那一边儿子为x 
	if(w)	fa[w] = y;													//若存在儿子w,则w的父亲为y 
	fa[y] = x;															//y的父亲更新为x 
	fa[x] = z;															//x的父亲更新为z 
	pushup(y);															//上传 
}

inline void splay(int x){									//把x旋转至splay的根节点 
	int y = x, z, cnt = 0;
	st[++cnt] = y;											//存放x到根的路径 
	while(nroot(y))	st[++cnt] = y = fa[y];
	while(cnt)	pushdown(st[cnt--]);						//先下放操作 
	while(nroot(x)){										//若x不是根节点 
		y = fa[x]; z = fa[y];								//y是x的父节点,z是y的父节点 
		if(nroot(y))
			rotate((son[y][0]==x)^(son[z][0]==y)?x:y);		//若z的y儿子与y的x儿子同向则旋转x,否则旋转y,只有这样才能改变结构 
		rotate(x);											//旋转x 
	}
	pushup(x);												//更新数据 
}

inline void access(int x){						//根节点与x节点放入一个Splay中,使两点由实边相连
	for(register int y = 0; x; x = fa[y=x]){	//记录下原节点
		splay(x);								//x转到根节点 
		son[x][1] = y;							//x的右儿子指向原来的点 
		pushup(x);								//更新 
	}
}

inline void makeroot(int x){					//将x转至原树的根节点,且使左子树为空
	access(x);									//将x与根节点连通 
	splay(x);									//转动使x到根,此时右子树为空 
	pushreverse(x);								//交换左右子树 
}

int findroot(int x){								//查找x在当前Splay中真实的根节点
	access(x);										//将x与根节点连通 
	splay(x);										//转动使x到根,此时右子树为空 
	while(son[x][0])	pushdown(x), x = son[x][0];	//向下搜索左子树直至没有 
	splay(x);										//保证时间复杂度 
	return x;
}

inline void split(int x, int y){				//找到x到y之间的通路且记录下区间
	makeroot(x);								//将x转至根 
	access(y);									//构建x到y的通路 
	splay(y);									//将y转至根 
}

inline void link(int x, int y){					//判断在x与y之间加边是否合法后加边
	makeroot(x);								//将x转到根 
	if(findroot(y) != x)	fa[x] = y;			//若y不在这个splay中即可加边 
}

inline void cut(int x, int y){							//判断在x与y之间删边是否合法后删边
	makeroot(x);										//将x转到根 
	if(findroot(y) == x && fa[y] == x && !son[y][0]){	//若符合条件 
		fa[y] = son[x][1] = 0;							//y的父亲节点直接指向x的右子树 
		pushup(x);										//更新信息 
	}
}

int main(){
	read(n), read(m);
	for(register int i = 1; i <= n; ++i)	read(a[i]);
	while(m--){
		read(op), read(x), read(y);
		if(op == 0){
			split(x, y);						//创立通路并统计 
			write(sumxor[y]);					//输出值 
			putchar('\n');
		}
		if(op == 1)	link(x, y);					//连接 
		if(op == 2)	cut(x, y);					//删除 
		if(op == 3){
			splay(x);							//将x转到根节点则修改后不会影响 
			a[x] = y;							//修改 
		}
	}
	return 0;
}

例题二:[国家集训队]Tree II

思路

\(LCT\) 版的线段树 \(2\),一个道理,先维护乘法再维护加法最后维护旋转标记即可。

\(Code\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int mod = 51061;
int n, m, x, y, c;
char op;
int size[100005], v[100005], mlz[100005], plz[100005], rlz[100005], fa[100005], son[100005][2], sum[100005];
int st[100005];

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f? x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10^48);
}

inline bool nroot(int u){
	return u == son[fa[u]][1] || u == son[fa[u]][0];
}

inline void pushadd(int u, int num){
	sum[u] = (sum[u] + size[u]*num) % mod;
	v[u] = (v[u] + num) % mod;
	plz[u] = (plz[u] + num) % mod;
}

inline void pushmul(int u, int num){
	sum[u] = (sum[u] * num) % mod;
	v[u] = (v[u] * num) % mod;
	plz[u] = (plz[u] * num) % mod;
	mlz[u] = (mlz[u] * num) % mod;
}

inline void pushreverse(int u){
	swap(son[u][0], son[u][1]);
	rlz[u] ^= 1;
}

inline void pushdown(int u){
	if(mlz[u] != 1){
		pushmul(son[u][0], mlz[u]), pushmul(son[u][1], mlz[u]);
		mlz[u] = 1;
	}
	if(plz[u]){
		pushadd(son[u][0], plz[u]), pushadd(son[u][1], plz[u]);
		plz[u] = 0;
	}
	if(rlz[u]){
		if(son[u][0])	pushreverse(son[u][0]);
		if(son[u][1])	pushreverse(son[u][1]);
		rlz[u] = 0;
	}
}

inline void pushup(int u){
	sum[u] = (sum[son[u][1]] + sum[son[u][0]] + v[u]) % mod;
	size[u]  = size[son[u][0]] + size[son[u][1]] + 1;
}

inline void rotate(int u){
	int v = fa[u], p = fa[v], k = son[v][1] == u, w = son[u][!k];
	if(nroot(v))	son[p][son[p][1] == v] = u;
	son[u][!k] = v;
	son[v][k] = w;
	if(w)	fa[w] = v;
	fa[v] = u;
	fa[u] = p;
	pushup(v);
}

inline void splay(int u){
	int v = u, cnt = 0, p;
	st[++cnt] = v;
	while(nroot(v))	st[++cnt] = v = fa[v];
	while(cnt)	pushdown(st[cnt--]);
	while(nroot(u)){
		v = fa[u], p = fa[v];
		if(nroot(v))	rotate((son[p][1] == v) ^ (son[v][1] == u)? u:v);
		rotate(u);
	}
	pushup(u);
}

inline void access(int u){
	for(register int v = 0; u; u = fa[v = u]){
		splay(u);
		son[u][1] = v;
		pushup(u);
	}
}

inline void makeroot(int u){
	access(u);
	splay(u);
	pushreverse(u);
}

inline void split(int u, int v){
	makeroot(u);
	access(v);
	splay(v);
}

inline void link(int u, int v){
	makeroot(u);
	fa[u] = v;
}

inline void cut(int u, int v){
	split(x, y);
	fa[u] = son[v][0] = 0;
}

inline void add(int u, int v, int num){
	split(u, v);
	pushadd(v, num);
}

inline void mul(int u, int v, int num){
	split(u, v);
	pushmul(v, num);
}

signed main(){
	read(n), read(m);
	for(register int i = 1; i <= n; ++i)	v[i] = size[i] = mlz[i] = 1;
	for(register int i = 1; i < n; ++i){
		read(x), read(y);
		link(x, y);
	}
	while(m--){
		op = getchar();
		if(op == '+'){
			read(x), read(y), read(c);
			add(x, y, c);
		}
		if(op == '-'){
			read(x), read(y);
			cut(x, y);
			read(x), read(y);
			link(x, y);
		}
		if(op == '*'){
			read(x), read(y), read(c);
			mul(x, y, c);
		}
		if(op == '/'){
			read(x), read(y);
			split(x, y);
			write(sum[y]);
			putchar('\n');
		}
	}
	return 0;
}
posted @ 2024-10-09 18:27  Zzzzzzzm  阅读(12)  评论(0)    收藏  举报