【数据结构】动态树

【数据结构】动态树

动态树(Link-Cut Tree),是OI中一种高级的数据结构,用于维护一个动态森林上的链上问题。性价比较高。

题目描述

给定 \(n\) 个点以及每个点的权值,要你处理接下来的 \(m\) 个操作。
操作有四种,操作从 \(0\)\(3\) 编号。点从 \(1\)\(n\) 编号。

  • 0 x y 代表询问从 \(x\)\(y\) 的路径上的点的权值的 \(\text{xor}\) 和。保证 \(x\)\(y\) 是联通的。
  • 1 x y 代表连接 \(x\)\(y\),若 \(x\)\(y\) 已经联通则无需连接。
  • 2 x y 代表删除边 \((x,y)\),不保证边 \((x,y)\) 存在。
  • 3 x y 代表将点 \(x\) 上的权值变成 \(y\)

这是链上维护\(xor\)和问题。

我们用\(splay\)维护一棵子树上的一条链的信息,也就是说,LCT是由多个\(splay\)构成的,这个\(splay\)有一个性质,中序遍历恰好是这条链从浅到深的顺序。这样的结构叫做“实链剖分”。实边构成一条链。

接下来介绍一些核心操作:

access

将一个点与到它这颗子树的根的道路打通。我们沿着\(fa\)指针向上跳,将当前点\(splay\)上去,然后将它变成它父亲(如果有的话)的右儿子,因为它的深度比它的父亲低。

inline void access(int x)
{
	for(int rc = 0;x;rc = x,x = t[x].fa)
		splay(x),t[x].son[1] = rc,update(x);
}

findroot

找到\(x\)所在子树的根。我们首先打通\(x\)到根的路径,再将\(x\ splay\)上去,由于中序遍历是深度序列,所以一定是最左边的点,找到即可。

inline int findroot(int x)
{
	access(x);splay(x);
	while(t[x].son[0]) pushdown(x),x = t[x].son[0];
	splay(x);
	return x;
}

makeroot

\(x\)变成所在子树的根。打通路径,\(splay\ x\),这时\(x\)一定是中序遍历最靠后的一个,打个标记将树翻转即可。(这就是用\(splay\)的原因。)

inline void makeroot(int x)
{
	access(x);splay(x);rever(x);
}

连接\(x,y\)所在的两棵子树,让\(x\)变成所在子树的根。再将\(y \to fa_x\)即可。

inline void link(int x,int y)
{
	makeroot(x);
	if(findroot(y) == x) return;
	t[x].fa = y;
}

cut

切断\(x,y\)中间的边。还是将\(x\)变成根。这时若是\(x,y\)有边,\(y\)一定是\(x\)的右儿子。所以\(y\)的父亲设为\(0\)\(x\)的右儿子设为\(0\)即可。

inline void cut(int x,int y)
{
	makeroot(x);
	if(findroot(y) != x || t[y].fa != x || t[y].son[0] != 0) return;
	t[y].fa = 0;t[x].son[1] = 0;
	update(x);
}

split

\(x,y\)之间的路径单独提出来计算答案。将\(x\)设为根,打通\(x,y\)的路径,再将\(y\ splay\)到根,\(y\)点的信息就是这个路径的信息。

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

综合下来,这道题就实现了。这只是LCT最基本的实现,更高级的应用还参考一些练习题。

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
struct LCT{
	int fa,son[2],val,sum,tag;
}t[N];
inline int which(int x){return x == t[t[x].fa].son[1];}
inline void update(int x){t[x].sum = t[t[x].son[0]].sum ^ t[t[x].son[1]].sum ^ t[x].val;}
inline void rever(int x){if(!x) return; swap(t[x].son[0],t[x].son[1]);t[x].tag ^= 1;}
inline void pushdown(int x){if(t[x].tag) rever(t[x].son[0]),rever(t[x].son[1]); t[x].tag = 0;}
inline bool isroot(int x){return t[t[x].fa].son[0] != x && t[t[x].fa].son[1] != x;}
inline void rorate(int x)
{
	int dir = which(x),y = t[x].fa,z = t[y].fa;
	if(t[x].son[dir ^ 1]) t[t[x].son[dir ^ 1]].fa = y;
	t[y].son[dir] = t[x].son[dir ^ 1];
	t[x].fa = z;
	if(!isroot(y)) t[z].son[which(y)] = x;
	t[y].fa = x;
	t[x].son[dir ^ 1] = y;
	update(x);
	update(y);
	if(!isroot(y)) update(z);
}
inline void splay(int x)
{
	int st[N],top = 0,now = x;
	while(!isroot(now)) st[++top] = now,now = t[now].fa; st[++top] = now;
	while(top) pushdown(st[top]),top--;
	while(!isroot(x))
	{
		if(!isroot(t[x].fa))
			rorate((which(x) ^ which(t[x].fa)) ? x : t[x].fa);
		rorate(x);
	}
	update(x);
}
inline void access(int x)
{
	for(int rc = 0;x;rc = x,x = t[x].fa)
		splay(x),t[x].son[1] = rc,update(x);
}
inline int findroot(int x)
{
	access(x);splay(x);
	while(t[x].son[0]) pushdown(x),x = t[x].son[0];
	splay(x);
	return x;
}
inline void makeroot(int x)
{
	access(x);splay(x);rever(x);
}
inline void link(int x,int y)
{
	makeroot(x);
	if(findroot(y) == x) return;
	t[x].fa = y;
}
inline void cut(int x,int y)
{
	makeroot(x);
	if(findroot(y) != x || t[y].fa != x || t[y].son[0] != 0) return;
	t[y].fa = 0;t[x].son[1] = 0;
	update(x);
}
inline void split(int x,int y)
{
	makeroot(x);access(y);splay(y);
}
inline int query(int x,int y)
{
	split(x,y);return t[y].sum;
}
int main()
{
	int n,m,op,x,y;
	cin>>n>>m;
	for(int i = 1;i <= n;i++) cin>>t[i].val;
	for(int i = 1;i <= m;i++)
	{
		cin>>op>>x>>y;
		if(op == 0) cout<<query(x,y)<<endl;
		else if(op == 1) link(x,y);
		else if(op == 2) cut(x,y);
		else if(op == 3) splay(x),t[x].val = y;
	}
	return 0;
}
posted @ 2023-08-18 20:57  The_Last_Candy  阅读(32)  评论(0编辑  收藏  举报