zoukankan      html  css  js  c++  java
  • 学习总结-树链剖分

    (一)什么是"树剖"

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

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

    (二)怎样"剖分"

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

    首先要了解几个概念:

    对于一个节点,定义:

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

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

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

    援引巨佬博客中的内容:

    树链剖分目的:树路径信息维护。
    将一颗树划分成若干条链,用数据结构去维护每条链,复杂度(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.树的统计

    留坑待补...

  • 相关阅读:
    yarn的工作原理
    105. Construct Binary Tree from Preorder and Inorder Traversal
    3. Longest Substring Without Repeating Characters
    2. Add Two Numbers
    windows相关
    《UNIX环境高级编程》源码配置——apue.3e 安装
    PB数据窗口只存储过程数据源创建
    PB连接ORALCE数据库方法
    PB 计算公式算出结果赋值给另外一列
    PowerBuilder中pbm_keydown()和pbm_dwnkey()的区别:
  • 原文地址:https://www.cnblogs.com/GDOI2018/p/13531836.html
Copyright © 2011-2022 走看看