「学习笔记」树链剖分

轻重链剖分

树链?剖分?

树链剖分,顾名思义是把一棵树剖分成若干条链。
轻重链剖分满足每个结点属于且只属于一条重链。

首先引出概念:

名称 概念
重儿子 父结点的子树结点数量最多的子结点
轻儿子 父结点中除了重儿子以外的儿子
重边 连接父结点和重儿子的边
轻边 连接父结点和轻儿子的边
重链 若干条重边连接而成的路径
轻链 若干条轻边连接而成的路径

1
现在有一棵树,对于该树,从根结点开始寻找重儿子,并且其重儿子也向下寻找重儿子,最终这些重边连成一条重链。而根结点其他的儿子(即轻儿子)也各自寻找自己的重儿子,使得整棵树分成若干条重链。
2
3
实现
剖分的过程由两个dfs实现。

名称 解释
fa[u] 结点u的父结点
dep[u] 结点u的深度
size[u] 以结点u为根的子树的结点数目
son[u] 结点u的重儿子
rk[u] 结点u的dfs标号在树中所对应的结点
top[u] 结点u所在链的顶端结点
id[u] 结点u的dfs序

1.\large\mathcal{1.}
标记每个结点的 father,dep,size,并找出每个结点的重儿子。

void dfs1(int u , int father , int depth) {
	fa[u] = father;//记录父结点 
	dep[u] = depth;//记录深度
	size[u] = 1;//将当前子树size标记为1
	
	for (int i = head[u]; i; i = edge[i].next) { 
		if (edge[i].to != father) {
			dfs1(edge[i].to , u , depth+1);//深度++ 
			
			size[u] += size[edge[i].to];//更新父结点的 size 
			
			if (size[edge[i].to] > size[son[u]]) { 
				son[u]=edge[i].to;//选取size最大的子结点作为重儿子 
			} 
		}
	}
}

调用:

dfs1(root , 0 , 1)

2.\large\mathcal{2.}
连接重链,标记dfs序,处理出数组top,id,rk

void dfs2(int u , int t) {
	top[u] = t;//通过标记当前链的链首来连接重链
	id[u] = ++cnt;//记录dfs序
	rk[cnt] = u;//记录dfs序指向的节点
	
	if (!son[u]) return;
	
	dfs2(son[u] , u);//优先延伸重链,使同一条重链上的dfs序连续
	
	for (int i = head[u]; i; i = edge[i].next) {
		if (edge[i].to != son[u] && fa[u]) {
			dfs2(edge[i].to , edge[i].to);
		}
	}
}

时间复杂度

树链剖分的两个性质:

  • 如果(u, v)是一条轻边,那么 size(v)<size(u)÷2\mathcal{size(v)<size(u)÷2};
  • 从根结点到任意结点的路所经过的轻重链的个数必定都小于 logn\log n.

可得,树链剖分的时间复杂度为 O(nlog2n)O(n\log^2n) 或者写做…… O(n(logn)2)O(n(\log n)^2)
显然是一种优秀的算法。

posted @ 2020-07-31 21:07  willbe233  阅读(57)  评论(0)    收藏  举报