学习总结-树链剖分

(一)什么是"树剖"

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

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

(二)怎样"剖分"

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

首先要了解几个概念:

对于一个节点,定义:

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

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

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

援引巨佬博客中的内容:

树链剖分目的:树路径信息维护。
将一颗树划分成若干条链,用数据结构去维护每条链,复杂度\(\Theta(n\log 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(n\log 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\to LCA(x,y)\to y\)

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

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

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

(五)几道例题

1.洛谷P3384轻重链剖分

2.树的统计

留坑待补...

posted @ 2020-08-19 20:51  __allenge  阅读(203)  评论(1编辑  收藏  举报