【详解】树链剖分之重链剖分

终于搞懂了树链剖分的一些皮毛了……

树链剖分

“树链剖分”,顾名思义,就是把一棵树剖分成一条条的链……

重链剖分

重链剖分的基本概念

重链剖分是树链剖分的一种,它会把树剖分成一条条重链……

什么是重链呢?

重链就是连接每一个树的重儿子所形成的链。

重儿子就是其儿子重以儿子为根的子树大小最大的儿子。

画一个图来理解一下:

对于这样一棵树,它剖成重链应该是这样的(红色是重链,绿色包裹的是重链上的点):

重链剖分的过程

首先,如果我们需要知道重儿子,那得知道子树的大小在进行处理,所以要分两次 dfs 来实现。

第一次 dfs

这一次 dfs 主要是算出每一个点的深度、父亲、子树大小。

就是一个简单的 dfs,很好理解。

局部代码
void dfs1(int x,int father,int deep)
{
	dep[x]=deep;
	fa[x]=father;
	siz[x]=1;
	int mxson=-1;
	for(auto y:g[x])
	{
		if(y==father)continue;
		dfs1(y,x,deep+1);
		siz[x]+=siz[y];
		if(siz[y]>mxson)
		{
			mxson=siz[y];
			son[x]=y;
		}
	}
}
第二次 dfs

算出每个点的链头、重儿子,以及其 dfs 序的编号(这个编号后续会有用)。

这一个比较简单的 dfs,应该也比较好理解。

局部代码
void dfs2(int x,int tf)
{
	id[x]=++cnt;
	top[x]=tf;
	if(!son[x])return;
	dfs2(son[x],tf);
	for(auto y:g[x])
	{
		if(y==fa[x]||y==son[x])continue;
		dfs2(y,y);
	}
}

以上就是重链剖分的基本步骤,时间复杂度 O(n),也是十分的高级好吧。

重链剖分的应用

学会了重链剖分的基本步骤,那还是得学会怎么用对吧……

先来看一道题目

洛谷 P3384 【模板】重链剖分/树链剖分icon-default.png?t=O83Ahttps://www.luogu.com.cn/problem/P3384

题目大意

解题思路

当然,一看题目的名称,就知道是重链剖分……

回顾重链剖分(这次加上每个点的 dfs 序编号):

也许你会发现,对于在同一个重链里面的点,他们的编号都是连续的

由此,我们可以把树看成一个个序列,从树上问题转换为序列问题

那题目中的每个操作都可以看做是在序列上进行的……

那么,很自然地就可以想到线段树可以解决它。

对于第 1 个操作

和倍增法求 LCA 类似地,我们可以依次往上跳,直到 x,y 都在同一条链上,每一次跳,都可以看做是在链上这个区间内加上了 z,直接套上线段树即可。

但是,最后也要记得处理最终的 x,y 之间的这个区间。

局部代码:

void update(int x,int y,int z)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		change(1,id[top[x]],id[x],z);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	change(1,id[x],id[y],z);
}
对于第 2 个操作

和第一个操作类似地,也是往上跳,直到 x,y 在同一条链上,只不过每次跳时都是对链这个区间进行一次求和查询,累加即可。

局部代码:

int que(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		ans+=query(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ans+=query(1,id[x],id[y]);
	return ans;
}
对于第 3 个操作

可以再次发现,同一颗子树内的点的编号是连续的,所以可以一次修改操作就行了。

修改的区间是 [id_x,id_x+size_x-1]id_x 是 x 的编号,size_x 是以 x 为子树的大小。

局部代码(好像没啥好展示的):

change(1,id[x],id[x]+siz[x]-1,z);
对于第 4 个操作

修改的区间一样,只是查询操作而已。

局部代码:

cout<<query(1,id[x],id[x]+siz[x]-1)<<"\n";

完整代码

记得取模!!!

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q,rt,mod;
int a[100001];
vector<int> g[100001];
int dep[100001];
int fa[100001];
int siz[100001];
int son[100001];
int id[100001],top[100001],wt[100001],cnt;
void dfs1(int x,int father,int deep)
{
	dep[x]=deep;
	fa[x]=father;
	siz[x]=1;
	int mxson=-1;
	for(auto y:g[x])
	{
		if(y==father)continue;
		dfs1(y,x,deep+1);
		siz[x]+=siz[y];
		if(siz[y]>mxson)
		{
			mxson=siz[y];
			son[x]=y;
		}
	}
}
void dfs2(int x,int tf)
{
	id[x]=++cnt;
	wt[id[x]]=a[x];
	top[x]=tf;
	if(!son[x])return;
	dfs2(son[x],tf);
	for(auto y:g[x])
	{
		if(y==fa[x]||y==son[x])continue;
		dfs2(y,y);
	}
}
struct tree{
	int sum,l,r,add;
}tr[400001];
void build(int u,int l,int r)
{
	tr[u]={0,l,r,0};
	if(l==r)
	{
		tr[u].sum=wt[l];
		tr[u].sum%mod;
		return;
	}
	int mid=l+r>>1;
	build(u*2,l,mid);
	build(u*2+1,mid+1,r);
	tr[u].sum=tr[u*2].sum+tr[u*2+1].sum;
	tr[u].sum%=mod;
}
void push_down(int u)
{
	if(tr[u].add)
	{
		tr[u*2].sum+=(tr[u*2].r-tr[u*2].l+1)*tr[u].add;
		tr[u*2].add+=tr[u].add;
		tr[u*2].sum%=mod;
	//	tr[u*2].add%=mod;
		tr[u*2+1].sum+=(tr[u*2+1].r-tr[u*2+1].l+1)*tr[u].add;
		tr[u*2+1].add+=tr[u].add;
		tr[u*2+1].sum%=mod;
	//	tr[u*2+1].add%=mod;
		tr[u].add=0;
	}
}
void push_up(int u)
{
	tr[u].sum=tr[u*2].sum+tr[u*2+1].sum;
	tr[u].sum%=mod;
}
void change(int u,int l,int r,int d)
{
	if(l<=tr[u].l&&tr[u].r<=r)
	{
		tr[u].sum+=(tr[u].r-tr[u].l+1)*d;
		tr[u].sum%=mod;
		tr[u].add+=d;
		return;
	}
	push_down(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid)
		change(u*2,l,r,d);
	if(r>mid)
		change(u*2+1,l,r,d);
	push_up(u);
}
int query(int u,int l,int r)
{
	if(l<=tr[u].l&&tr[u].r<=r)
	{
		return tr[u].sum;
	}
	push_down(u);
	int mid=tr[u].l+tr[u].r>>1;
	int res=0;
	if(l<=mid)
		res+=query(u*2,l,r);
	res%=mod;
	if(r>mid)
		res+=query(u*2+1,l,r);
	res%=mod;
	return res;
}
void update(int x,int y,int z)
{
	z%=mod;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		change(1,id[top[x]],id[x],z);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	change(1,id[x],id[y],z);
}
int que(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		ans+=query(1,id[top[x]],id[x]);
		ans%=mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ans+=query(1,id[x],id[y]);
	ans%=mod;
	return ans;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin>>n>>q>>rt>>mod;
	
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	
	int u,v;
	for(int i=1;i<n;i++)
	{
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	dfs1(rt,0,1);
	dfs2(rt,rt);
	
	build(1,1,n);
	
	int op,x,y,z;
	while(q--)
	{
		cin>>op;
		if(op==1)
		{
			cin>>x>>y>>z;
			update(x,y,z);
		}
		if(op==2)
		{
			cin>>x>>y;
			cout<<que(x,y)<<"\n";
		}
		if(op==3)
		{
			cin>>x>>z;
			change(1,id[x],id[x]+siz[x]-1,z);
		}
		if(op==4)
		{
			cin>>x;
			cout<<query(1,id[x],id[x]+siz[x]-1)%mod<<"\n";
		}
	}
}

最后求赞勿喷。

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

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

相关文章

RocketMQ: 部署结构与存储特点

RocketMQ 是什么 它是一个队列模型的消息中间件&#xff0c;具有高性能、高可靠、高实时、分布式特点 Producer、Consumer、队列都可以分布式Producer 向一些队列轮流发送消息 队列集合称为 TopicConsumer 如果做广播消费则一个 consumer 实例消费这个 Topic 对应的所有队列如果…

帮助中心FAQ系统:打造卓越客户服务体验的关键驱动力

在当今这个信息爆炸的时代&#xff0c;企业为了保持市场竞争力&#xff0c;必须不断提升客户服务体验。FAQ&#xff08;常见问题解答&#xff09;系统&#xff0c;作为一种高效且便捷的用户服务工具&#xff0c;正日益受到企业的青睐。本文将阐述FAQ系统的核心价值、功能特性以…

如何使用 Python 开发一个简单的文本数据转换为 Excel 工具

目录 一、准备工作 二、理解文本数据格式 三、开发文本数据转换为Excel工具 读取CSV文件 将DataFrame写入Excel文件 处理其他格式的文本数据 读取纯文本文件&#xff1a; 读取TSV文件&#xff1a; 四、完整代码与工具封装 五、使用工具 六、总结 在数据分析和处理的…

Elasticsearch向量搜索:从语义搜索到图搜图只有一步之遥

续 上集说到语义搜索&#xff0c;这集接着玩一下图搜图&#xff0c;这种场景在电商中很常见——拍照搜商品。图搜图实现非常类似语义搜索&#xff0c;代码逻辑结构都很类似… 开搞 还是老地方modelscope找个Vision Transformer模型&#xff0c;这里选用vit-base-patch16-224…

Flink【基于时间的双流联结 Demo】

前言 1、基于时间的双流联结&#xff08;Join&#xff09; 对于两条流的合并&#xff0c;很多情况我们并不是简单地将所有数据放在一起&#xff0c;而是希望根据某个字段的值将它们联结起来&#xff0c;“配对”去做处理。例如用传感器监控火情时&#xff0c;我们需要将大量温度…

大数据入门-什么是Flink

这里简单介绍Flink的概念、架构、特性等。至于比较详细的介绍&#xff0c;会单独针对这个组件进行详细介绍&#xff0c;可以关注博客后续阅读。 一、概念 Apache Flink 是一个框架和分布式处理引擎&#xff0c;用于在无边界和有边界数据流上进行有状态的计算。 Flink的四大基…

KubeVirt下gpu operator实践(GPU直通)

KubeVirt下gpu operator实践(GPU直通) 参考《在 KubeVirt 中使用 GPU Operator》&#xff0c;记录gpu operator在KubeVirt下实践的过程&#xff0c;包括虚拟机配置GPU直通&#xff0c;容器挂载GPU设备等。 KubeVirt 提供了一种将主机设备分配给虚拟机的机制。该机制具有通用性…

How to update the content of one column in Mysql

How to update the content of one column in Mysql by another column name? UPDATE egg.eggs_record SET sold 2024-11-21 WHERE id 3 OR id 4;UPDATE egg.eggs_record SET egg_name duck egg WHERE id 2;

【K8S系列】imagePullSecrets配置正确,但docker pull仍然失败,进一步排查详细步骤

如果 imagePullSecrets 配置正确,但在执行 docker pull 命令时仍然失败,可能存在以下几种原因。以下是详细的排查步骤和解决方案。 1. 检查 Docker 登录凭证 确保你使用的是与 imagePullSecrets 中相同的凭证进行 Docker 登录: 1.1 直接登录 在命令行中,执行以下命令: …

机器学习基础06

目录 1.梯度下降 1.1梯度下降概念 1.2梯度下降公式 1.3学习率 1.4实现梯度下降 1.5API 1.5.1随机梯度下降SGD 1.5.2小批量梯度下降MBGD 1.6梯度下降优化 2.欠拟合过拟合 2.1欠拟合 2.2过拟合 2.3正则化 2.3.1L1正则项&#xff08;曼哈顿距离&#xff09; 2.3.2…

徒手从零搭建一套ELK日志平台

徒手从零搭建一套ELK日志平台 日志分析的概述日志分析的作用主要收集工具集中式日志系统主要特点采集日志分类ELK概述初级版ELK终极版ELK高级版ELKELK收集日志的两种形式 搭建ELK平台Logstash工作原理Logstash核心概念环境准备安装部署docker添加镜像加速器安装部署Elasticsear…

开源科学工程技术软件介绍 – EDA工具KLayout

link 今天向各位知友介绍的 KLayout是一款由德国团队开发的开源EDA工具。 KLayout是使用C开发的&#xff0c;用户界面基于Qt。它支持Windows、MacOS和Linux操作系统。安装程序可以从下面的网址下载&#xff1a; https://www.klayout.de/build.html KLayout图形用户界面&…

Linux离线安装Docker命令,简单镜像操作

解压安装包 首先&#xff0c;使用 tar 命令解压 docker-27.3.1.tgz 安装包&#xff1a; tar -zxvf docker-27.3.1.tgz 将二进制文件移动到可执行路径上的目录 接着&#xff0c;将解压出来的 Docker 二进制文件复制到系统的可执行路径&#xff08;通常是 /usr/bin/&#xff09…

Redis中常见的数据类型及其应用场景

五种常见数据类型 Redis中的数据类型指的是 value存储的数据类型&#xff0c;key都是以String类型存储的&#xff0c;value根据场景需要&#xff0c;可以以String、List等类型进行存储。 各数据类型介绍&#xff1a; Redis数据类型对应的底层数据结构 String 类型的应用场景 常…

redis中的set类型及常用命令

集合就是把一些有关联的数据放到一起。与list不同的是&#xff0c;集合中的顺序不重要&#xff0c;变换了元素的顺序&#xff0c;仍是同一个集合。集合中的元素是不能重复的。和list类似&#xff0c;集合中的每个元素&#xff0c;也都是string类型。 关于集合的相关命令 sadd/…

Python的顺序表

一、脑图 二、封装一个顺序表的类 1.构造函数 class SeqList:#显性定义出构造函数def __init__(self,capacity 10):#初始化顺序表 &#xff0c;设置初始容量和已有元素self.capacity capacity #线性表的最大容量self.size 0 #已存储的元素个数self.data [None]*capacity…

OpenCV从入门到精通实战(九)——基于dlib的疲劳监测 ear计算

本文实现Python库d和OpenCV来实现眼部闭合检测&#xff0c;主要用于评估用户是否眨眼。 步骤一&#xff1a;导入必要的库和设置参数 首先&#xff0c;代码导入了必要的Python库&#xff0c;如dlib、OpenCV和scipy。通过argparse设置了输入视频和面部标记预测器的参数。 from…

windows下,用CMake编译qt项目,出现错误By not providing “FindQt5.cmake“...

开发环境&#xff1a;windows10 qt5.14&#xff0c; 编译器msvc2017x64&#xff0c;CMake3.30&#xff1b; 现象&#xff1a; CMakeList文件里&#xff0c;如有find_package(Qt5 COMPONENTS Widgets REQUIRED) target_link_libraries(dis_lib PRIVATE Qt5::Widgets) 用CMak…

基于SpringBoot+Vue的影院管理系统(含演示视频+运行截图+说明文档)

web启动链接地址&#xff1a; http://localhost:8082&#xff08;管理端&#xff09; http://localhost:8081&#xff08;用户端&#xff09; http://localhost:8082&#xff08;员工端&#xff09; 一、项目介绍 基于框架的系统&#xff0c;系统分为用户、员工和管理员三个…

SpringBoot3+Vue3开发图书馆管理系统

1 项目介绍 图书馆管理系统&#xff0c;管理图书、用户、借书、还书、实时监测归还是否逾期&#xff0c;逾期未归还会生成违规记录。违规状态不可借阅图书。需缴纳罚金&#xff0c;消除违规记录。可动态设置图书最多累计借阅数量上限和最长借阅天数上限&#xff0c;当用户满足…