【图论】树链剖分

本篇博客参考:

  • 【洛谷日报#17】树链剖分详解
  • Oi Wiki 树链剖分

文章目录

    • 基本概念
    • 代码实现
    • 常见应用
      • 路径维护:求树上两点路径权值和
      • 路径维护:改变两点最短路径上的所有点的权值
      • 求最近公共祖先

基本概念

首先,树链剖分是什么呢?

简单来说,就是把一棵树分成很多条链,然后利用数据结构(线段树、树状数组)维护链上的信息

下面是一些定义:

  • 重子结点:父亲结点的所有儿子结点中子树结点数目最多的结点称为重子结点
  • 轻子结点:父亲结点的所有儿子中除了重子结点的其他结点称为轻子结点

如果某个结点是叶子结点,那么它既没有重子结点也没有轻子结点

  • 重边:父亲结点和重子结点连成的边
  • 轻边:父亲结点和轻子结点连成的边
  • 重链:多条重边连接成的链
  • 轻链:多条轻边连接成的链

落单的点也当做重链,那整棵树就会被分成若干条重链,类似这样:(图源Oi Wiki)
在这里插入图片描述
下面是一些变量声明:

  • fa[u] 结点 u 的父亲结点
  • dep[u] 结点 u 的深度
  • sz[u] 以结点 u 为根的子树的结点个数
  • son[u] 结点 u 的重儿子
  • top[u] 结点 u 所在链的顶端结点
  • dfn[u] 结点 u 在 dfs 中的执行顺序,同时也是树链剖分后的新编号,可以理解为dfs序的映射
  • id[u] dfn 标号 u 对应的结点编号,有 id[dfn[u]] == u

树链剖分的一些性质

  • 重链开头的结点不一定是重子结点(因为每一个非叶子结点不管是重子结点还是轻子结点都有重边)
  • 剖分时重链优先遍历,最后的 dfs 序中(也就是 dfn 数组),重链的 dfs 序时连续的,按 dfs 序排序后的序列就是剖分后的链
  • 时间复杂度 O ( l o g n ) O(logn) O(logn)

代码实现

接下来需要实现树链剖分,也就是把每个结点划到一条链里,这通常是由两边 dfs 来实现的

第一遍 dfs

目的:处理 fa[u] dep[u] sz[u] son[u]

void dfs1(int u, int father, int depth) // u: 当前结点  fa: 父结点  depth: 当前深度
{
	fa[u] = father; // 更新当前结点父结点
	dep[u] = depth; // 更新当前结点深度
	sz[u] = 1; // 子树大小初始化为1
	for (int i = 0; i < g[u].size(); i ++ )
	{
		int j = g[u][i]; // 子结点编号
		if (j == father) continue;
		dfs1(j, u, depth + 1);
		sz[u] += sz[j]; // 用子结点的sz更新父结点的sz
		if (sz[j] > sz[son[u]]) son[u] = j; // 更新重子结点
	}
}

第二遍 dfs

目的:处理 top[u] dfn[u] id[u]

void dfs2(int u, int tt) // u: 当前结点  tt: 重链顶端结点
{
	top[u] = tt; // 更新当前结点所在重链顶端
	dfn[u] = ++ cnt; // 更新dfs序
	id[cnt] = u; // 更新dfs序的映射
	if (!son[u]) return; // 叶子结点 直接退出
	// 优先遍历重子结点 目的是保证链上各个结点的dfs序连续
	// 当前结点的重子结点和当前结点在同一条链上 所以链的顶端都是tt
	dfs2(son[u], tt); 
	for (int i = 0; i < g[u].size(); i ++ )
	{
		int j = g[u][i]; // 子结点编号
		if (j == son[u] || j == fa[u]) continue; // 遇到重子结点或者父结点就跳过
		dfs2(j, j); // j点位于轻链顶端 它的top必然是本身
	}
}

常见应用

两遍 dfs 之后,就已经完成了树链剖分的操作,但是由于本人举一反三能力缺失根本不知道应该怎么用,所以后面再放几个常见的使用情况

路径维护:求树上两点路径权值和

这里做的是一个类似LCA的操作,如果两个结点不在同一条链上,就让深度更大的结点往上跳(每次只能跳一个结点,避免两个结点一起跳导致擦肩而过)直到跳到同一条链上,因为同一条链上的点 dfs 序是相邻的,所以可以直接在这条链上用数据结构计算权值和(下面的代码用的是线段树)

int sum(int x, int y) // xy表示待求的两点路径权值和
{
	int ans = 0;
	int tx = top[x], ty = top[y]; // tx ty分别表示x和y所在重链的顶端结点
	while (tx != ty) // 让x和y跳到同一条链上
	{
		if (dep[x] >= dep[y]) // x比y更深 让x先跳
		{
			ans += query(dfn[tx], dfn[x]); // query是线段树的区间求和函数
			x = fa[tx], tx = top[x]; // 让x跳到原先链顶端的父结点 更新tx
		}
		else
		{	
			ans += query(dfn[ty], dfn[y]); // query是线段树的区间求和函数
			y = fa[ty], ty = top[y]; // 让y跳到原先链顶端的父结点 更新ty
		}
	}
	// 循环结束 x和y终于到了同一条链 但是二者不一定是同一个结点 所以还需要计算两点之间的贡献
	if (dfn[x] <= dfn[y]) ans += query(dfn[x], dfn[y]);
	else ans += query(dfn[y], dfn[x]);
	return ans;
}

路径维护:改变两点最短路径上的所有点的权值

和上面的求最短路径权值和很像,都是先让两个点跳到同一条链上再进行计算

void update(int x, int y, int c) // 把x与y的最短路上所有点的权值都加上c
{
	int tx = top[x], ty = top[y];
	while (tx != ty)
	{
		if (dep[tx] >= dep[ty])
		{
			modify(dfn[tx], dfn[x], c); // modify是线段树区间修改的函数
			x = fa[tx], tx = top[x]; // 让x跳到原先链顶端的父结点 更新tx
		}
		else
		{
			modify(dfn[ty], dfn[y], c); // modify是线段树区间修改的函数
			y = fa[ty], ty = top[y]; // 让y跳到原先链顶端的父结点 更新ty
		}
	}
	// 循环结束 x和y终于到了同一条链 但是二者不一定是同一个结点 所以还需要对两点之间的结点进行修改
	if (dfn[x] <= dfn[y]) modify(dfn[x], dfn[y], c);
	else modify(dfn[y], dfn[x], c);
}

求最近公共祖先

思路就是,如果两个点不在一条重链上,那就不断让深度大的结点往上跳,直到跳到同一条链上,那么深度较小的点就是LCA

int lca(int u, int v) // 求u和v的lca
{
	while (top[u] != top[v]) // 如果u和v不在同一条链上就一直让深度大的点往上跳
	{
		if (dep[top[u]] > dep[top[v]]) u = fa[top[u]];
		else v = fa[top[v]];
	}
	return dep[u] > dep[v] ? v : u; // 深度小的结点就是lca
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/466540.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

centos7.9的GUI桌面样式不符合默认熟悉的操作习惯

一、问题描述&#xff1a; 原因&#xff1a;桌面样式选错了。 二、解决&#xff1a; 1.先登进去LogOut。 2.点击设置的工具图标中的GNOME Classic即可恢复成默认操作习惯的桌面样式。 3.恢复到默认熟悉的操作界面

基于有限状态机开发健壮的Nodejs/TCP客户端

有限状态机是一种数学计算模型&#xff0c;它描述了在任何给定时间只能处于一种状态的系统的行为。形式上&#xff0c;有限状态机有五个部分&#xff1a; 初始状态值 (initial state)有限的一组状态 (states)有限的一组事件 (events)由事件驱动的一组状态转移关系 (transition…

浏览器如何查看http请求的报文?

HTTP协议用于从WWW服务器传输超文本到本地浏览器的传送协议。 它可以使浏览器更加高效&#xff0c;使网络传输减少。 它不仅保证计算机正确快速地传输超文本文档&#xff0c;还确定传输文档中的哪一部分&#xff0c;以及哪部分内容首先显示 (如文本先于图形)等。所以在node.js里…

viple拓展题

数数问题 题目&#xff1a;使用viple来实现程序&#xff0c;使得运行结果能将数字逐个数出即可 思路&#xff1a;首先&#xff0c;数数字&#xff0c;不知道用户具体要求数到多少结束&#xff0c;所以&#xff0c;可以采用简单的对话活动来实现与用户的交互。其次&#xff0c;…

Linux系统安装1Panel管理面板并实现无公网IP远程管理本地服务器

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

string类型的使用以及编码方式

Redis 中所有的键的类型都是字符串类型&#xff0c;⼀个字符串的最⼤值不能超过 512 MB。 由于 Redis 内部存储字符串完全是按照⼆进制流的形式保存的&#xff0c;所以 Redis 是不处理字符集编码问题的&#xff0c;客⼾端传⼊的命令中使⽤的是什么字符集编码&#xff0c;就存储…

放在你后口袋里的七种基本质量工具

以下是我反复看到的七种质量改进工具。这些质量工具中的大多数已经存在了一段时间&#xff0c;但这肯定不会降低它们的价值&#xff01; 这些工具最大的优点是使用非常简单&#xff0c;并且可以在中快速使用Minitab统计软件或者从事&#xff0c;但你当然可以使用其他方法&…

【已解决】MySQL:常用的除法运算+精度处理+除数为0处理

目录 问题现象&#xff1a; 问题分析&#xff1a; 拓展&#xff1a; 1、除法运算&#xff1a; 拓展&#xff1a;MySQL中常用的几种除法运算 1、取整除法 2、浮点数除法 3、取余除法 4、向上取整除法 5、向下取整除法 2、运算结果的精度处理 1.1、浮点数 1.2、总位数 1.3、…

c++的const总结(转)

为什么使用const&#xff1f;采用符号常量写出的代码更容易维护&#xff1b;指针常常是边读边移动&#xff0c;而不是边写边移动&#xff1b;许多函数参数是只读不写的。const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替)&#xff0c;分类如下&#xff1a;…

什么是网站?为什么要搭建网站?

网站&#xff1a;简单来说&#xff0c;网站就是通过互联网来展示信息的页面集合。它可以在电脑或者手机上打开&#xff0c;提供各种功能&#xff0c;比如查看新闻、购买商品、搜索信息等。 一、建网站的目的&#xff1a;展示个人或企业的存在 网站建设的首要目的之一是展示个人…

十年追光者 | 一段人生故事,聆听《康岛天香》创始人李波的成长历程

​为了响应国家乡村振兴&#xff0c; 绿水青山就是金山银山&#xff0c; 中药材复兴等 国家政策&#xff0c;来自老家河南的李波女士&#xff0c;如今在海南澄迈县桥头镇 开启了她的沉香创业故事&#xff0c; 沉香树一直以来被誉为海南第四颗树&#xff0c; 从最早 20 亩沉香…

java入门 -输入和输出

输入输出 开发中大量会使用输入和输出&#xff0c;今天来总结一下Java语法阶段常使用的输入和输出。 输出 System.out 控制台输出信息。 不换行显示一行&#xff1a; System.out.print( ); System.out.print("hello "); System.out.print("java!");运行结…

JMeter压测SpringBoot项目

压力测试架构图如下: 配置JMeter 在JMeter的bin目录,双击jmeter.bat 新建一个测试计划,并右键添加线程组: 进行配置 一共会发生4万次请求。 ctrl + s保存; 添加http请求: 配置http请求: 配置断言,来判断当前请求是否成功: 正常响应如下:

Tomcat Session ID---会话保持

简单拓补图 一、负载均衡、反向代理 7-1nginx代理服务器配置 [rootdlnginx ~]#yum install epel-release.noarch -y ###安装额外源[rootdlnginx ~]#yum install nginx -y[rootdlnginx ~]#systemctl start nginx.service[rootdlnginx ~]#systemctl status nginx.service [ro…

机器人路径规划:基于Bug算法的机器人路径规划(提供Python代码)

一、Bug算法简介 Bug 算法是一种基于追踪障碍物的路径规划算法&#xff0c;它模拟了一种昆虫寻找巢穴的行为&#xff0c;因此得名Bug算法。Bug算法的基本思路是&#xff1a;当机器人遇到障碍物时&#xff0c;他会沿着障碍物的边缘行走&#xff0c;直到到达目标点。该算法可以分…

如何定期清理数据库中的无效数据?

企业的数据库在运行相当长一段时间后&#xff0c;都会出现无效数据的堆积&#xff0c;这些数据包含了过时、重复、错误、缺失&#xff08;空字段&#xff09;的数据&#xff0c;长期占据着宝贵的数据库空间。而在上云热潮的推动下&#xff0c;绝大多数企业已经将他们的业务数据…

Ingress 基于URL路由多个服务

文章目录 前言一、基于请求地址转发不同应用的pod1.创建一个nginx的pod和一个apache的pod及其各自的service2.创建ingress实现一个地址两个path分别访问nginx和apache3.验证根据域名web2.study.com的两个路径/foo和/bar来访问到不同的pod4.分别在nginx和apache的pod里创建网站目…

张驰咨询为领益智造打造精益六西格玛管理体系,助力苹果全球供应链优化

近日&#xff0c;领益智造精益六西格玛项目在总部启动&#xff0c;作为领益智造精益六西格玛管理战略合作伙伴&#xff0c;张驰咨询精益六西格玛咨询顾问团队已进驻领益智造总部&#xff0c;指导学员项目选择。作为管理创新的重要举措&#xff0c;领益智造在中国各大区超过10个…

1.MongoDB的特点与应用场景

什么是 MongoDB &#xff1f; MongoDB 是基于 C 开发的 NOSQL 开源文档数据库 &#xff0c;是最像关系型数据库的 nosql&#xff0c;功能也是最丰富的 nosql&#xff0c;它具有所以的可伸缩性&#xff0c;灵活性&#xff0c;高性能&#xff0c;高扩展性的优势。 大致有如下特…