【算法详解】 树链剖分

一、问题引入:

1、给你一个序列,再给你一堆询问区间,对于每个询问区间,请你求区间内的最大值、累加和等等。

对于这个问题,我们是早就做烂的了,线段树、树状数组等数据结构都能轻松求,这里不再详述。


2、给你一棵树,再给你一堆询问,每次给你两个点,让你求两个点之间的路径中的点权最大值、点权和等等。

对于这个问题,我们很显然不能再像问题 1 1 1一样样直白的去做,因为树的路径与纯粹的区间不同。那我们能否用一个算法,将一棵树剖分成若干个区间(链),在用这些区间用 1 1 1一样的方法去求的。

是的,这个算法就叫做——

树链剖分。


二、原理详解

我们将一棵树剖分成若干条链,而每条链如果我们能用区间的方式去求答案,最基本的要求就是链上的每个点的编号是连续的,这也是一个区间的基本条件。
那么如何保证链上的点的编号连续呢?

我们引入以下几种概念:
1、重儿子: s i z e size size最多的儿子
2、轻儿子:除了重儿子之外的儿子
3、重链:以重儿子为首的链
4、轻链:同理。
5、 t o p [ x ] top[x] top[x]:当前点 x x x所处的重链的编号(即重儿子的编号)
6、 n u m [ x ] : num[x]: num[x]:遍历到 x x x的dfs序

预处理:

对于上述问题,我们可以以下处理:
一旦遇到重儿子,就优先遍历重儿子,直到结束。对于每个轻儿子,也是优先遍历重儿子,直到结束……如此循环,就能保证每一条链都是连续的。

通过这样的操作我们就把一棵树转化为了若干段区间。
如下图所示:
在这里插入图片描述
四个颜色分别对应着四条链,点上的编号是dfs序。

这是预处理部分,代码如下:

void dfs1(int x,int dd,int faa){
	int Max = 0;
	d[x] = dd;
	fa[x][0] = faa;
	siz[x] = 1;
	for (int i = linkk[x]; i; i = e[i].Next){
	    int y = e[i].y;
	    if (y == faa) continue;
		dfs1(e[i].y,dd+1,x);
		siz[x]+=siz[y];
		if (Max < siz[y]) Max = siz[son[x] = y];//重儿子
	}
}

void dfs2(int x,int id){
	num[x] = ++cnt; top[x] = id;//dfs序以及重链的顶端
	if (son[x]) dfs2(son[x] , id);//优先遍历重链
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (y == fa[x][0] || y == son[x]) continue;
		dfs2(y,y);//遍历轻链
	}
}

答案求解:

给你两个点 x x x, y y y,让你求 x , y x,y x,y路径之间的最大值、权值和。

类似于倍增求 L c a Lca Lca的思想,如果两个点不在同一条链上,我们就将深度大的点不断往上跳,直到将两个点跳到同一条链上为止。

假设深度大的点编号为 x x x,那么往上跳一次,对答案的代价是:
A s k ( n u m [ t o p [ x ] ] , n u m [ x ] ) Ask(num[top[x]],num[x]) Ask(num[top[x]],num[x])
即当前链的答案。
Ask函数的具体内容据题目而定,一般用线段树或者树状数组来维护。
x x x y y y跳到同一深度后,贡献就是:
A s k ( x , y ) Ask(x,y) Ask(x,y)
x x x y y y深度的先后顺序据题目而定,切记不可搞错。

模板如下:

int Ask(int x,int y){
	int ans = 0;
	while (top[x] != top[y]){
		if (d[top[x]] < d[top[y]]) swap(x,y);//深度大的先跳
		ans+=tr.Ask(num[x]) - tr.Ask(num[top[x]] - 1);//问题查询,此处为树状数组
		x = fa[top[x]][0];//跳到当前重链的父亲
	}
	if (d[x] < d[y]) swap(x,y);
	ans+=tr.Ask(num[x]) - tr.Ask(num[y]-1);
	return ans;
}

以上两部分就是树链剖分的核心内容。


总之

树链剖分就是将树上路径问题转化为序列问题的过程,对于求解各种树上路径问题有莫大的帮助。

大致复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)


例题讲解:

详见

这篇好博客

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值