浅谈重链剖分

重链剖分是一种树的划分算法,用于解决树上的求∑和求max/min等问题。它将树分为多条链,每点只属于一条链,并利用线段树维护链上的信息。常见应用包括路径上的维护、子树维护和求最近公共祖先(LCA)。通过两次DFS求解相关属性,如节点的父亲、深度、子树数量等。
摘要由CSDN通过智能技术生成

重链剖分

前置知识:

  • 线段树, d f s dfs dfs序,基本的树上知识,效率高的存图方法(如链式前向星、邻接表)

1. 概念(chě dàn):

一种对进行划分的算法,它先通过重链剖分将树分为多条
保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、线段树等)来维护每一条链。
一般用于解决树上的 ∑ \sum max ⁡ / min ⁡ \max/\min max/min 等问题。

2.定义(源于OI-Wiki):

重子节点(重儿子): 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
轻子节点(轻儿子): 除重子节点外的所有节点。
重边: 从某个结点到重子节点的边。
轻边: 从某个结点到轻子节点的边。
重链: 若干条首尾衔接的重边构成的边。
Tips: 落单的结点也当作重链。
如图:
在这里插入图片描述
∙ \bullet f a [ x ] : fa[x]: fa[x]:节点 x x x在树上的父亲。
∙ \bullet d e p [ x ] : dep[x]: dep[x]:节点 x x x在树上的深度。
∙ \bullet s i z [ x ] : siz[x]: siz[x]:节点 x x x的子树的数量。
∙ \bullet s o n [ x ] : son[x]: son[x]:节点 x x x的重儿子。
∙ \bullet t o p [ x ] : top[x]: top[x]:节点 x x x所在重链的顶部节点。
∙ \bullet i d [ x ] : id[x]: id[x]:节点 x x x d f s dfs dfs序。
然后会用两次 d f s dfs dfs求解, d f s 1 dfs1 dfs1求解前三个 d f s 2 dfs2 dfs2求解后三个

void dfs1(int x, int f, int deep) {
    //x当前节点,f父亲,deep深度 
	dep[x] = deep, fa[x] = f, siz[x] = 1;  //深度,父亲,子树大小 
	int maxson = -1;  //求重儿子 
	for (int i = head[x]; i; i = E[i].next) {
     //遍历 
		int v = E[i].to;
		if (v == f) continue;  //如果下一条边是父亲,那么就不遍历了,我们要找的是儿子 
		dfs1(v, x, deep + 1);  //遍历儿子 
		siz[x] = siz[x] + siz[v];  
		if (siz[v] > maxson)  //找重儿子 
			son[x] = v, maxson = siz[v];
	}
}

void dfs2(int x, int Top) {
     //x当前节点,Top是x所在重链顶部的节点
	id[x] = ++cnt, wt[cnt] = w[x], top[x] = Top;  //dfs序,重链顶部的点 
	if (!son[x]) return;  //优先找重儿子 
	dfs2(son[x], Top);  //遍历重儿子 
	for (int i = head[x]; i; i = E[i].next) {
     //遍历 
		int v = E[i].to;
		if (v == fa[x] || v == son[x]) continue;  //找完重儿子,再去找轻儿子 
		dfs2(v, v);
	}
} 

3.性质(重链)

1. 1. 1.树上的每个节点都属于且仅属于一条重链。
2. 2. 2.所有的重链把树完全剖分。
3. 3. 3.在剖分时,优先遍历重边、重儿子。
4. 4. 4.一条链上的 d f s dfs dfs序是连续的。
5. 5. 5.每一个子树上的 d f s dfs dfs序是连续的。

4.常见应用

① ① 路径上的维护
  • 用树链的性质来求树上的边权点权
    int Query_Range(int x, int y) {
         
    	int ans = 0;
    	while (top[x] ^ top[y]) {
          //如果不在同一条链上
    		if (dep[top[x]] < dep[top[y]]) swap(x, y); //从深度大的往深度小的跳
    		ans = ans + Query(1, id[top[x]], id[x]);  //统计链上的权值
    		x = fa[top[x]];  //往高处跳
    	}
    	if (dep[x] > dep[y]) swap(x, y);  //线段树需要满足 L <= R
    	ans = (ans + Query(1, id[x], id[y]));  //跳到最后肯定会在同一条链上
    	return ans % P;
    }
    
    根据第 4 4 4条性质可知,在链上的 d f s dfs dfs序时连续的,所以我们可以用线段树进行修改、维护,每次选择深度较大的链往上跳,在跳的同时统计权值。
② ② 子树维护
  • 比如说求以 x x x根节点子树上的边权点权
    int Query_Son(int x) {
         
    	return Query(1, id[x], id[x] + siz[x] - 1);//子树大小的计算包括自己,所以-1
    }
    
    根据第 5 5 5条性质可知,在子树上的 d f s dfs dfs序也是连续的,所以直接用线段树修改、维护就行了。
③ ③ 求LCA
  • 求两个点的LCA
    int lca(int u, int v) {
         
    	while (top[u] ^ top[v]) {
           //不在同一条链上
    		if (dep[top[u]] > dep[top[v]])   //往高处跳
      			u = fa[top[u]];
    		else
    	  		v = fa[top[v]];
    	}
    	return dep[u] > dep[v] ? v : u;  //往高处跳
    }
    

5.例题

P2590 [ZJOI2008]树的统计(模板)

题目描述:

一棵树上有 n n n 个节点,编号分别为 1 1 1 n n n,每个节点都有一个权值 w w w
我们将以下面的形式来要求你对这棵树完成一些操作:
I . I. I. CHANGE u t : 把节点 u u u 的权值改为 t t t
I I . II. II. QMAX u v: 询问从点 u u u 到点 v v v 的路径上的节点的最大权值。
I I I . III. III. QSUM u v: 询问从点 u u u 到点 v v v 的路径上的节点的权值和。
注意:从点 u u u 到点 v v v 的路径上的节点包括 u u u v v v 本身

输入格式:

输入文件的第一行为一个整数 n n n,表示节点的个数。
接下来 n − 1 n-1 n1 行,每行 2 2 2 个整数 a a a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值