树链剖分/重链剖分

树链剖分是一种将树形结构转化为线性结构的方法,用于简化处理。它定义了重儿子和轻儿子的概念,重边和轻边,以及重链。通过重链剖分,可以高效地处理点权和修改点权等问题。博客提供了洛谷P3384题目的重链剖分模板,包括dfs1()和dfs2()函数,以及如何处理路径和子树的点权和修改。
摘要由CSDN通过智能技术生成

前言

首先感谢洛谷大佬zengqinyi,受益匪浅,%%%
大佬详解链接:https://www.cnblogs.com/chinhhh/p/7965433.html

树链剖分

温馨提示

先把 LCA、树形DP、DFS序和线段树学了会方便理解一些。

概念及作用

树链剖分就是对一棵树分成几条链,把树形变为线性,减少处理难度。

重儿子和轻儿子

对于每一个非叶子节点,它的儿子中儿子数量最多的那一个儿子为该节点的重儿子,非重儿子的剩下所有儿子即为轻儿子。
叶子节点没有重儿子也没有轻儿子(因为它没儿子。。。)

重边和轻边

连接任意两个重儿子的边叫做重边,剩下的即为轻边。

重链

相邻重边连起来的连接一条重儿子的链叫重链。每一条重链以轻儿子为起点。
对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链。
在这里插入图片描述

【模板】重链剖分/树链剖分

参考题目:洛谷P3384 【模板】重链剖分/树链剖分
原题链接:https://www.luogu.com.cn/problem/P3384

题目

题目描述

如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。
3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z。
4 x 表示求以 x 为根节点的子树内所有节点值之和。

输入格式

第一行包含 4 个正整数 N,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含 N 个非负整数,分别依次表示各个节点上初始的数值。
接下来 N−1 行每行包含两个整数 x,y,表示点 x 和点 y 之间连有一条边(保证无环且连通)。
接下来 M 行每行包含若干个正整数,每行表示一个操作。

输出格式

输出包含若干行,分别依次表示每个操作 2 或操作 4 所得的结果(对 P 取模)。

输入输出样例

输入 #1
5 5 2 24
7 3 7 8 0 
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
输出 #1
2
21

题解

dfs1()

作用:
1.标记每个点的深度(dp[]数组).
2.标记每个点的父亲(fa[]数组)
3.标记每个非叶子节点的子树大小(含它自己)(size[]数组)
4.标记每个非叶子节点的重儿子编号(son[]数组)

inline void dfs1(int x,int f,int dep)//x为当前节点,f为父亲,dep为深度
{
   
	dp[x]=dep;//标记深度
	fa[x]=f;//标记父亲
	size[x]=1;//标记非叶子节点的子树大小(含己)
	int maxson=-1;//记录重儿子的儿子数
	for(register int i=head[x];i;i=nex[i])
	{
   
		int y=to[x];
		if(y==f) continue;//为父亲则continue
		dfs1(y,x,dep+1);//dfs儿子
		siz[x]+=siz[y];//加上儿子的儿子数
		if(siz[y]>maxson)//标记非叶子节点的重儿子编号
		{
   
			son[x]=y,maxson=siz[y];
		}
	}
}

dfs2()

作用:
1.标记每个点的新编号(id[]数组)
2.赋值每个点的初始值到新编号上(初始值w[]数组,新编号值wt[]数组)
3.处理每个点所在链的顶端(top[]数组)
4.处理每条链

inline void dfs2(int x,int topf)//x为当前节点,topf为当前链的最顶端的节点
{
   
	id[x]=cnt++;//标记点的新编号
	wt[cnt]=w[x];//赋值初始值到新编号上
	top[x]=topf;//处理点所在链的顶端
	if(!son[x]) return;//没儿子(只有叶子节点没重儿子)返回
	dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理
	for(register int i=head[x];i;i+=nex[i])
	{
   
		int y=to[x];
		if(y==fa[x]||y==son[x]) continue;
		dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链	
	}
}

处理问题(Attention)

前面说dfs2的顺序是先处理重儿子再处理轻儿子
我们来模拟一下(图):
在这里插入图片描述
解释:因为顺序是先重再轻,所以每一条重链的新编号是连续的;因为是dfs,所以每一个子树的新编号也是连续的。

需要处理的问题

1.修改任意两点间路径上的点权
2.处理任意两点间路径上的点权和
3.修改一点及其子树的点权
4.处理一点及其子树的点权和

处理任意两点间路径
求点权和

设所在链顶端的深度更深的那个点为x点.
1.ans加上x点到x所在链顶端这一段区间的点权和.
2.把x跳到x所在链顶端的那个点的上面一个点.
不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可.
在这里插入图片描述
这时我们注意到,我们所要处理的所有区间均为连续编号(新编号),于是用线段树处理连续编号区间和。
每次查询的时间复杂度为O(log2n)。

inline int qRange(int x,int y)
{
   
	int ans=0;
	while(top[x]!=top[y])//x和y不在同一条链
	{
   
		if(dp[top[x]]<dp[top[y]]) swap(x,y);//把x改为深度更深的链的点
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到链顶端的点权和
		ans+=res;
		ans%=mod;//题目要求取模
		x=fa[top[x]];//把x跳到x所在链顶端节点的父亲
	}//使x和y在同一条链上
	if(dp[x]>dp[y]) swap(x,y);//把x改为深度更深的点
	res=0;
	query(1,1,n,id[x],id[y]);//加上两个点的区间点权和
	ans+=res;
	return ans%=mod;
}
修改点权

区间修改和区间查询差不多,用update同时记一下要加的值。

inline void updaRange(int x,int y,int k)//x,y为路径首尾,k为要加的值
{
   
	while(top[x]!=top[y])//同上个代码块
	{
   
		if(dp[top[x]]<dp[top[y]]) swap(x,y);//同上个代码块
		update(1,1,n,id[top[x]],i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值