【学习笔记】Link Cut Tree(动态树)学习笔记

前置知识:splay虚实链剖分

将一棵树虚实链剖分,剖分成若干条链,每一条链存在一个splay里面,这个splay里面的边由实边构成,splay与splay之间如果有边,连的是虚边
一个splay的中序遍历即这一条链从深度小到深度大的顺序

核心操作:access(x):把x到根节点这条链提出来放在一个splay中

常见问题模型:维护链上的信息、动态维护连通性(只连不断)、维护边权(化边为点)、维护子树信息、维护树上染色连通块(每一个splay代表一个染色连通块)等

模板:【模板】Link Cut Tree (动态树)

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

const int N = 100005;
int n, m, res, g[N], val[N], sum[N], fa[N], son[N][2];
bool lz[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;	
}

inline void write(int x)
{
	if (!x)
	{
		puts("0");
		return;
	}
	int num = 0;
	char sc[15];
	while (x) sc[++num] = x % 10 + 48, x /= 10;
	while (num) putchar(sc[num--]);
	putchar('\n');
}

inline void push_up(int x)//上传标记
{
	sum[x] = sum[son[x][0]] ^ sum[son[x][1]] ^ val[x];
}

inline void rese(int x)//翻转
{
	lz[x] ^= 1, son[x][0] ^= son[x][1] ^= son[x][0] ^= son[x][1];
}

inline void push_down(int x)//下传懒标记
{
	lz[x] = 0;
	if (son[x][0]) rese(son[x][0]);
	if (son[x][1]) rese(son[x][1]);
}

inline bool check(int x)//判断x和它父亲是否在同一个splay内
{
	return son[fa[x]][0] == x || son[fa[x]][1] == x;
}

inline void rotate(int x)//旋转x到它的父亲
{
	int y = fa[x], z = fa[y];
	bool p = son[y][1] ^ x;
	if (check(y)) son[z][son[z][1] == y] = x;//son[z][1]==y 不能写成son[z][0]^y 因为son[z][1]==y时(son[z][1]==y)=1,而(son[z][0]^y)>0,可能>1 
	fa[x] = z, fa[y] = x;
	if (son[x][p]) fa[son[x][p]] = y;
	son[y][p ^ 1] = son[x][p], son[x][p] = y;
	push_up(y), push_up(x);
}

inline void splay(int x)//把x旋转到当前splay的根
{
	int y = x;
	g[res = 1] = x;
	while (check(y)) y = fa[y], g[++res] = y;
	while (res)
	{
		if (lz[g[res]]) push_down(g[res]);
		--res;
	}
	while (check(x))
	{
		y = fa[x];
		if (check(y)) rotate(((son[y][0] == x) ^ (son[fa[y]][0] == y)) ? x : y);
		rotate(x);
	}
}

inline void access(int x)//把根节点到x这条链提出来到一个splay,中序遍历为根节点到x
{
	for (Re i = 0; x; x = fa[i = x])
		splay(x), son[x][1] = i, push_up(x);
}

inline void make_root(int x)//让x成为原树上的根
{
	access(x), splay(x), rese(x);
}

inline int find_root(int x)//返回x所在原树上的根
{
	access(x), splay(x);
	while (1)
	{
		if (lz[x]) push_down(x);
		if (!son[x][0]) return x;
		x = son[x][0];
	}
}

inline int get_ans(int x, int y)//x到y这条链上权值的异或和
{
	make_root(x), access(y), splay(y);
	return sum[y];
}

inline void link(int x, int y)//如果x和y不联通的话,连接x和y
{
	make_root(x);
	if (find_root(y) ^ x) fa[x] = y, push_up(y);
}

inline void cut(int x, int y)//如果原树上存在边(x,y),删除掉这条边
{
	make_root(x), access(y), splay(y);
	if (fa[x] == y && !son[x][1]) fa[x] = son[y][0] = 0, sum[y] = val[y];
}

int main()
{
	n = read(), m = read();
	for (Re i = 1; i <= n; ++i) val[i] = sum[i] = read();
	for (Re i = 0; i < m; ++i)
	{
		int opt = read(), x = read(), y = read();
		if (!opt) write(get_ans(x, y));
		else if (opt == 1) link(x, y);
		else if (opt == 2) cut(x, y);
		else splay(x), val[x] = y, push_up(x);
	}
	return 0;
}

例题:
Sol】1. [SDOI2017]树点涂色:洛谷P3703LOJ#2001

Sol】2. [SHOI2014]三叉神经树:洛谷P4332LOJ#2187

参考资料:

  1. LCT总结——概念篇
  2. LCT总结——应用篇
posted @ 2021-03-04 16:55  clfzs  阅读(118)  评论(0)    收藏  举报