Link-cut-tree(动态树)

原理
https://blog.csdn.net/weixin_30872337/article/details/98507056
这篇文章对原理和实现都讲的挺好的。

正确性
这个不是很好理解。一棵树中,LCT用一个splaytree表示一条链,按照结点的深度顺序存储,所以无论splaytree怎么旋转,这条链内节点的相对位置不变,即原始树中的链没动,只是splaytree转了。不同的链由虚边相连,虚边只有可能变成实边,不会改变链接的结点,所以已经链接好的两个结点也不会在旋转中发生改变。
所以LCT中的一系列,除cut,link外,其他操作不会改变已经连好的结构。

实现


#include<cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int fa[maxn], c[maxn][2], rev[maxn], que[maxn];

inline bool isroot(int x) {
	return c[fa[x]][0] != x && c[fa[x]][1] != x;
}
void rotate(int u) {
	bool k = c[fa[u]][1] == u;
	int x = fa[u], y = fa[x];
	fa[u] = y;
	if (!isroot(x)) c[y][c[y][1] == x] = u;
	c[x][k] = c[u][!k]; fa[c[u][!k]] = x;
	c[u][!k] = x; fa[x] = u;
}
void pushdown(int x) {
	if (rev[x]) {
		if (c[x][0]) {
			swap(c[c[x][0]][0], c[c[x][0]][1]);
			rev[c[x][0]] ^= 1;
		}
		if (c[x][1]) {
			swap(c[c[x][1]][0], c[c[x][1]][1]);
			rev[c[x][1]] ^= 1;
		}
		rev[x] = 0;
	}
}

void splay(int u) {
	int top = 0;
	que[++top] = u;
	for (int uu=u; !isroot(uu); uu = fa[uu]) que[++top] = fa[uu];
	while (top) pushdown(que[top--]);
	int x, y;
	while (!isroot(u)) {
		x = fa[u], y = fa[x];
		if (!isroot(x)) c[y][0] == x ^ c[x][0] == u ? rotate(u) : rotate(x);
		rotate(u);
	}
}
void access(int u) {
	int rc = 0;
	while (u) {
		splay(u);
		c[u][1] = rc;
		rc = u;
		u = fa[u];
	}
}

void make_root(int u) {
	access(u);
	splay(u);
	rev[u] ^= 1;
	swap(c[u][0], c[u][1]);
}

int findroot(int u) {
	access(u);
	splay(u);
	while (c[u][0]) {
		pushdown(c[u][0]);
		u = c[u][0];
	}
	splay(u); //这里不知道为什么要spaly
	return u;
}

void link(int x, int y) {
	make_root(x);
	fa[x] = y;
}

void cut(int x, int y) {
	make_root(x);
	access(y);
	splay(y);
	c[y][0] = fa[x] = 0;
}

bool check(int x, int y) {
	make_root(x);
	return findroot(y) == x;
}

在findroot操作中,最后一个splay不知道为什么,但是不加的话有的题会超时。网上有的模板加有的不加
比如下面这个题。

几个题

2852: 我大哥
很简单的一个题,但是发现在findroot操作中,最后一个splay不加就会超时,想不明白。

#include<iostream>
#include<limits.h>
#include<algorithm>
#include<functional>
#include<cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int fa[maxn], c[maxn][2], rev[maxn], que[maxn];
int root[maxn];
inline bool isroot(int x) {
	return c[fa[x]][0] != x && c[fa[x]][1] != x;
}
void rotate(int u) {
	bool k = c[fa[u]][1] == u;
	int x = fa[u], y = fa[x];
	fa[u] = y;
	if (!isroot(x)) c[y][c[y][1] == x] = u;
	c[x][k] = c[u][!k]; fa[c[u][!k]] = x;
	c[u][!k] = x; fa[x] = u;
}
void pushdown(int x) {
	if (rev[x]) {
		if (c[x][0]) {
			swap(c[c[x][0]][0], c[c[x][0]][1]);
			rev[c[x][0]] ^= 1;
		}
		if (c[x][1]) {
			swap(c[c[x][1]][0], c[c[x][1]][1]);
			rev[c[x][1]] ^= 1;
		}
		rev[x] = 0;
	}
}

void splay(int u) {
	int top = 0;
	que[++top] = u;
	for (int uu=u; !isroot(uu); uu = fa[uu]) que[++top] = fa[uu];
	while (top) pushdown(que[top--]);
	int x, y;
	while (!isroot(u)) {
		x = fa[u], y = fa[x];
		if (!isroot(x)) c[y][0] == x ^ c[x][0] == u ? rotate(u) : rotate(x);
		rotate(u);
	}
}
void access(int u) {
	int rc = 0;
	while (u) {
		splay(u);
		c[u][1] = rc;
		rc = u;
		u = fa[u];
	}
}

void make_root(int u) {
	access(u);
	splay(u);
	rev[u] ^= 1;
	swap(c[u][0], c[u][1]);
}

int findroot(int u) {
	access(u);
	splay(u);
	while (c[u][0]) {
		pushdown(c[u][0]);
		u = c[u][0];
	}
	splay(u);
	return u;
}

void link(int x, int y) {
	make_root(x);
	fa[x] = y;
}

void cut(int x, int y) {
	make_root(x);
	access(y);
	splay(y);
	c[y][0] = fa[x] = 0;
}

bool check(int x, int y) {
	make_root(x);
	return findroot(y) == x;
}
int main()
{
	char cmd[15];
	int m, n;
	int x, y;
	scanf("%d%d", &m, &n);
	for (int i = 0; i < n; ++i) {
		scanf("%s%d%d", cmd, &x, &y);
		if (cmd[0] == 'c') {
			if (root[x]) cut(root[x], x);
			link(x, y);
			root[x] = y;
		}
		else {
			printf("%s\n", check(x, y) ? "Yes" : "No");
		}
	}
	return 0;
}

P2590 [ZJOI2008]树的统计
加树边,求最大值,求和,没有删除操作,其实可以用树剖做。
注意点是要先初始化权重,再进行连边操作。

/*
P2590 [ZJOI2008]树的统计
https://www.luogu.org/problem/P2590
*/

#include<iostream>
#include<limits.h>
#include<algorithm>
#include<functional>
#include<cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int fa[maxn], c[maxn][2], rev[maxn], que[maxn], cnt[maxn], w[maxn], mx[maxn];
int xs[maxn], ys[maxn];
inline bool isroot(int x) {
	return c[fa[x]][0] != x && c[fa[x]][1] != x;
}

void up(int x)
{
	mx[x] = max(max(mx[c[x][0]], mx[c[x][1]]), w[x]);
	cnt[x] = cnt[c[x][0]] + cnt[c[x][1]] + w[x];
}

void rotate(int u) {
	bool k = c[fa[u]][1] == u;
	int x = fa[u], y = fa[x];
	fa[u] = y;
	if (!isroot(x)) c[y][c[y][1] == x] = u;
	c[x][k] = c[u][!k]; fa[c[u][!k]] = x; 
	c[u][!k] = x; fa[x] = u;
	up(x);
	up(u);
}
void pushdown(int x) {
	if (rev[x]) {
		if (c[x][0]) {
			swap(c[c[x][0]][0], c[c[x][0]][1]);
			rev[c[x][0]] ^= 1;
		}
		if (c[x][1]) {
			swap(c[c[x][1]][0], c[c[x][1]][1]);
			rev[c[x][1]] ^= 1;
		}
		rev[x] = 0;
	}
}

void splay(int u) {
	int top = 0;
	que[++top] = u;
	for (int uu=u; !isroot(uu); uu = fa[uu]) que[++top] = fa[uu];
	while (top) pushdown(que[top--]);
	int x, y;
	while (!isroot(u)) {
		x = fa[u], y = fa[x];
		if (!isroot(x)) c[y][0] == x ^ c[x][0] == u ? rotate(u) : rotate(x);
		rotate(u);
	}
}
void access(int u) {
	int rc = 0;
	while (u) {
		splay(u);
		c[u][1] = rc;
		up(u);
		rc = u;
		u = fa[u];
	}
}

void make_root(int u) {
	access(u);
	splay(u);
	rev[u] ^= 1;
	swap(c[u][0], c[u][1]);
}

void link(int x, int y) {
	make_root(x);
	fa[x] = y;
}
void penguins(int u, int neww) {
	splay(u);
	w[u] = neww;
	up(u);
}
int getsum(int x, int y) {
	make_root(x);
	access(y);
	splay(x);
	return cnt[x];
}
int getmax(int x, int y) {
	make_root(x);
	access(y);
	splay(x);
	return mx[x];
}
int main()
{
	int n, q, x, y;
	char cmd[15];
	scanf("%d", &n);
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &xs[i], &ys[i]);
	}
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &w[i]);
		cnt[i] = w[i];
		mx[i] = w[i];
	} 
	for (int i = 1; i < n; ++i) {
		link(xs[i], ys[i]);
	}
	mx[0] = -50000;
	scanf("%d", &q);
	for (int i = 0; i < q; ++i) {
		scanf("%s %d %d", cmd, &x, &y);
		if (cmd[1] == 'M') printf("%d\n", getmax(x, y));
		else if (cmd[1] == 'S') {
			printf("%d\n", getsum(x, y));
			
		}
		else penguins(x, y);
	}
	return 0;
}

P2387 [NOI2014]魔法森林

一个图,可能有自环和重边,每条边两个权重A,B,要求是从点1到点n,找一条路径,这条路径中最大的A与最大的B之和最小。
思路是用并查集维护连通情况(LCT判断是否连通很慢)(只要连通1和n就可以),把所有边按照A从小到大排序,依次把每条边加入最小生成树中,如果这条边的两个点x,y之前未连接,直接加入。否则取出x,y之间B最大的那条边,如果B大于待加入的边的B,就删掉图中的边,加入新的边,不然就不加。
两个点是否连通用并查集,找权值B最大的边用LCT,加边的方式是把边作为一个点,加一条边link两次link(x,边);link(边,y),所有点的权值为0。

正确性:
当前待加入的边的A肯定大于最小生成树中所有的边的A,如果待加入的边的B也大于取出的边B,那这条边肯定不用加。
如果当前的最小生成树还没有连通1和n,待加入的边又无法拓展新的点(因为构成了环路),那么肯定还需要加入更多的边才有可能连通1和n。假设刚好连通1到n时加入的边为k,那此时的输出就是k.A+图中最大的B,之前的A都不起作用,只有B起作用。所以A可以理解为一个定值,只能缩小B来减小答案。
(代码实现是按照B排序的)

#include<iostream>
#include<limits.h>
#include<algorithm>
#include<functional>
#include<cstdio>
using namespace std;
const int maxn = 2e5 + 5;
int fa[maxn], c[maxn][2], rev[maxn], que[maxn], w[maxn], mx[maxn], uni[maxn];
struct edge {
	int x, y, a, b;
	bool operator<(edge &n) {
		return b < n.b;
	}
};
edge es[maxn];
inline bool isroot(int x) {
	return c[fa[x]][0] != x && c[fa[x]][1] != x;
}
int cx(int x) {
	if (uni[x] != x) uni[x] = cx(uni[x]);
    return uni[x];
}

void zm(int x, int y) {
	int r1 = cx(x), r2 = cx(y);
	if (r1 != r2) uni[r1] = r2;
}

void up(int x)
{
	mx[x] = x;
	if (w[mx[c[x][0]]] > w[mx[x]]) mx[x] = mx[c[x][0]];
	if (w[mx[c[x][1]]] > w[mx[x]]) mx[x] = mx[c[x][1]];
}

void rotate(int u) {
	bool k = c[fa[u]][1] == u;
	int x = fa[u], y = fa[x];
	fa[u] = y;
	if (!isroot(x)) c[y][c[y][1] == x] = u;
	c[x][k] = c[u][!k]; fa[c[u][!k]] = x;
	c[u][!k] = x; fa[x] = u;
	up(x);
	up(u);
}
void pushdown(int x) {
	if (rev[x]) {
		if (c[x][0]) {
			swap(c[c[x][0]][0], c[c[x][0]][1]);
			rev[c[x][0]] ^= 1;
		}
		if (c[x][1]) {
			swap(c[c[x][1]][0], c[c[x][1]][1]);
			rev[c[x][1]] ^= 1;
		}
		rev[x] = 0;
	}
}

void splay(int u) {
	int top = 0;
	que[++top] = u;
	for (int uu = u; !isroot(uu); uu = fa[uu]) que[++top] = fa[uu];
	while (top) pushdown(que[top--]);
	int x, y;
	while (!isroot(u)) {
		x = fa[u], y = fa[x];
		if (!isroot(x)) c[y][0] == x ^ c[x][0] == u ? rotate(u) : rotate(x);
		rotate(u);
	}
}
void access(int u) {
	int rc = 0;
	while (u) {
		splay(u);
		c[u][1] = rc;
		up(u);
		rc = u;
		u = fa[u];
	}
}

void make_root(int u) {
	access(u);
	splay(u);
	rev[u] ^= 1;
	swap(c[u][0], c[u][1]);
}


void link(int x, int y) {
	make_root(x);
	fa[x] = y;
}

void cut(int x, int y) {
	make_root(x);
	access(y);
	splay(y);
	c[y][0] = fa[x] = 0;
	up(y);
}

int getmax(int x, int y) {
	make_root(x);
	access(y);
	splay(x);
	return mx[x];
}
int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d%d%d", &es[i].x, &es[i].y, &es[i].a, &es[i].b);
	}
	sort(es + 1, es + 1 + m);
	for (int i = n + 1; i <= n + m; ++i) w[i] = es[i - n].a;
	for (int i = 1; i <= n; ++i) uni[i] = i;
	for (int i = 1; i <= n + m; ++i) mx[i] = i;

	int x, y, a, b, u, v, k;
	int ans = INT_MAX;
	for (int i = 1; i <= m; ++i) {
		bool add = true;
		x = es[i].x, y = es[i].y;
		if (cx(x) == cx(y)) {
			k = getmax(x, y);
			if (w[k] > es[i].a) cut(es[k - n].x, k), cut(k, es[k - n].y);
			else add = false;
		}
		else zm(x, y);
		if (add) link(x, i + n); link(i + n, y);
		if (cx(1) == cx(n))
			ans = min(ans, es[i].b + w[getmax(1, n)]);
	}
	if (ans < INT_MAX) printf("%d\n", ans);
	else printf("-1\n");
	return 0;
}

P4219 [BJOI2014]大融合

一棵树找一条边切断,两棵子树size相乘。
需要维护每个结点对应的子树和,包括实边和虚边。
询问的时候makeroot一个结点,然后access,splay另一个结点,(s[y] - s[x]) * s[x]就是答案了,y是根节点,x是左结点。(si[x] + 1) * (si[y] + 1)也可以,si是存每个结点所有虚边对应的size和。

注意点:因为是要求子树的和,所以在link的时候要考虑向上更新。一定要先access并且splay才行,因为涉及到了虚边,所以需要更新到整棵树的根。实际上在access的时候,把一个结点连到父节点的右边时,父节点的总子节点数是不变的,相当于本来的右子树变成虚的,本来虚的变成了右子树。但如果link的时候没有向上更新,相当于父节点记录的虚子树结点个数比实际上的要少了,这样就不对了。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 2e5 + 5;
int fa[maxn], c[maxn][2], rev[maxn], que[maxn], s[maxn], si[maxn];
inline bool isroot(int x) {
	return c[fa[x]][0] != x && c[fa[x]][1] != x;
}
void up(int x)
{
	s[x] = s[c[x][0]] + s[c[x][1]] + 1 + si[x];
}

void rotate(int u) {
	bool k = c[fa[u]][1] == u;
	int x = fa[u], y = fa[x];
	fa[u] = y;
	if (!isroot(x)) c[y][c[y][1] == x] = u;
	c[x][k] = c[u][!k]; fa[c[u][!k]] = x;
	c[u][!k] = x; fa[x] = u;
	up(x);
	up(u);
}
void pushdown(int x) {
	if (rev[x]) {
		if (c[x][0]) {
			swap(c[c[x][0]][0], c[c[x][0]][1]);
			rev[c[x][0]] ^= 1;
		}
		if (c[x][1]) {
			swap(c[c[x][1]][0], c[c[x][1]][1]);
			rev[c[x][1]] ^= 1;
		}
		rev[x] = 0;
	}
}

void splay(int u) {
	int top = 0;
	que[++top] = u;
	for (int uu = u; !isroot(uu); uu = fa[uu]) que[++top] = fa[uu];
	while (top) pushdown(que[top--]);
	int x, y;
	while (!isroot(u)) {
		x = fa[u], y = fa[x];
		if (!isroot(x)) c[y][0] == x ^ c[x][0] == u ? rotate(u) : rotate(x);
		rotate(u);
	}
}
void access(int u) {
	int rc = 0;
	while (u) {
		splay(u);
		si[u] += s[c[u][1]] - s[rc];
		c[u][1] = rc;
		up(u);//如果初始化s全部为1的话就不用这个了。没有初始化的话s全是0,
			//单个结点link的时候make_root会调用access,把结点的s变成1。
		rc = u;
		u = fa[u];
	}
}

void make_root(int u) {
	access(u);
	splay(u);
	rev[u] ^= 1;
	swap(c[u][0], c[u][1]);
}

void link(int x, int y) {
	make_root(x);
	access(y);
	splay(y);
	fa[x] = y;
	si[y] += s[x];
	up(y);
}

long long int getmax(int x, int y) {
	make_root(x);
	access(y);
	splay(y);
	//return (si[x] + 1)*(si[y] + 1);
	return (s[y] - s[x])*s[x];
}
int main()
{
	int n, m, x, y;
	scanf("%d%d", &n, &m);
	char cmd[4];
	for (int i = 0; i < m; ++i) {
		scanf("%s%d%d", cmd, &x, &y);
		if (cmd[0] == 'A') link(x, y);
		else printf("%lld\n", getmax(x, y));
	}
	return 0;
}

题目列表参考了https://blog.csdn.net/qq_36551189/article/details/79152612

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值