学习总结-树链剖分

(一)什么是"树剖"

对于解决一类树上的问题,可以将树上问题转化为区间上问题求解。树链剖分是指将
树剖成链以解决一些树上难以解决的问题。

那么,问题来了,应该按照怎样的规则将树剖分?

(二)怎样"剖分"

本文中的树链剖分按照"重链"进行剖分。

首先要了解几个概念:

对于一个节点,定义:

重儿子:该节点的所有子节点中,包含子节点节点数目最大的节点(一个节点最多一个)
轻儿子:该节点除重儿子以外的子节点
重边:连接该节点与重儿子的边
轻边:连接该节点与轻儿子的边
重链:由重边相接组成的链
轻链:由轻边相接组成的链

树链剖分一种剖分方式是重链剖分,使重链上的节点在重新编号后成为连续的一段区间,以方便维护。

按照"重链"剖分其实是一种启发式剖分。此外,还有按照"长链"剖分。

援引巨佬博客中的内容:

树链剖分目的:树路径信息维护。
将一颗树划分成若干条链,用数据结构去维护每条链,复杂度(Theta(nlog n))

划轻重链目的:为了让链上节点的编号连续。
我们在用线段树维护链的时候,如果节点编号不连续,那么就无法用线段树。

(三)编程实现剖分

第一步,遍历整棵树,将每个节点,每条边的信息处理出来。

需要处理的信息有:

(f[x]):记录节点(x)的父节点
(siz[x]):记录以节点(x)根的子树大小
(son[x]):记录节点(x)的重儿子
(d[x]):记录节点(x)的深度
(top[x]):记录(x)所在的重链的顶端节点(即(x)所在重链上深度最浅的节点。如果(x)不在重链上,那么(top[x] = x))
(id[x] = y):记录节点(x)的新编号(y)
(rak[y] = x):记录新编号(y)对应的节点(x)

对于前四个信息,很容易维护,只需要从根节点递归遍历整棵树即可。代码如下:

void dfs1(int x, int fa, int deep){
	f[x] = fa, d[x] = deep, siz[x] = 1;
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == fa) continue;
		dfs1(y, x, deep+1);
		siz[x] += siz[y];
		if(siz[y] > siz[son[x]] or son[x] == 0) son[x] = y;
	}
	return ;
}

对于后面两组信息,我们需要解决的问题是,如何保证重链上的节点有连续的一段编号?

联想到(dfs)序的性质,只要每次递归遍历的时候优先遍历重儿子,给其编号,就能保证重链上的节点有连续的一段编号了。

对于轻儿子,我们可以将其看作只有一个节点的重链。代码如下:

void dfs2(int x, int t){//t记录x所在重链的顶端节点
	top[x] = t, id[x] = ++cnt;
	if(!son[x]) return ;
	dfs2(son[x], t);
	for(int i=head[x]; i; i=Next[i]){
		int y = ver[i];
		if(y == son[x] or y == f[x]) continue;
		dfs2(y, y);
	}
	return ;
}

至此,整棵树剖分已完毕。我们得到了一个序列,不难探究这个序列的性质。

由于我们是按照求(dfs)序的方法对节点编号,因此,这个序列具有(dfs)序的特点。

特点一:对于一棵子树,它所有节点的编号是连续的。

同时,由于我们优先为重儿子编号,该序列还具有的特点是:

特点二:对于一条重链,它所有节点的编号是连续的。

(四)树链剖分的运用

1.求树上两个节点的(LCA)(最近公共祖先)

众所周知,(LCA)可以使用倍增求解。时间复杂是预处理(Theta(nlog n))加上每次询问(Theta(log n))

如果使用树链剖分求解(LCA),可以更高效。那么,如何运用呢?

(LCA)的倍增方法是将两个节点根据预处理出来的数组不断地向上

树链剖分也预处理了一个指向(x)祖先的数组(top[x]),是否可以仿照(LCA)的模式,不断向上呢?

答案显然是可以的。

如果(x),(y)一直向上走,肯定会走到一个公共节点。为了避免(x,y)"擦肩而过",每次我们只让其中一个向上走。

怎么走呢?如果(x,y)不在一条重链上,那么就让深度较深的节点走到该条重链顶端的父节点,交错着走,直到(x,y)在一条重链上。

如果(x,y)在一条重链上,那么它们的(LCA)显然是它们两个中深度较浅的节点。

可以证明,剖分出来的路径不超过(Theta(log n))条,因此,树链剖分求(LCA)的时间复杂度是:预处理(Theta(n))加上查询(Theta(log n))

2.维护树上一条路径上的点权或边权

设这条路径的两个端点分别为(x,y)。由树的性质可以知道,(x,y)之间只存在唯一的一条路径。这条路径是(x o LCA(x,y) o y)

因此,我们只需要在求(LCA(x,y))的同时,将经过的所有重链进行维护即可。

假设用线段树进行区间维护,那么时间复杂度为:

线段树区间修改复杂度(Theta(log n)),树剖求(LCA(x,y))时间复杂度(Theta(log n)),总时间复杂度(Theta(log^2n))

(五)几道例题

1.洛谷P3384轻重链剖分

2.树的统计

留坑待补...

原文地址:https://www.cnblogs.com/GDOI2018/p/13531836.html