树链剖分学习笔记

news2025/1/12 10:51:04

前言

树链剖分这个东西呢,简而言之就是把一些树上的操作转换成线性的问题。那看起来平平无奇的树链剖分,为什么很多人就是喜欢使用他呢,那想必肯定是有原因的,我们先卖个关子,先看一下树链剖分怎么写,原理是什么。

前置芝士

  • 线段树
  • 树形dp
  • dfs序
    如果不会可以看往期的学习笔记

算法详解

树链剖分的话我们就讲个一道模版题,毕竟也是一种方法,可以选择性使用。

洛谷模板 P3384

如题,已知一棵包含 N N N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 x x x y y y 结点最短路径上所有节点的值都加上 z z z

  • 2 x y,表示求树从 x x x y y y 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 x x x 为根节点的子树内所有节点值都加上 z z z

  • 4 x 表示求以 x x x 为根节点的子树内所有节点值之和

概念简介

首先,因为后面在讲解的时候会涉及到一些名词,我们来解释一下。

  • 重子:对于每一个非叶子节点,它的儿子中以那个儿子为根的子树节点数最大的儿子为该节点的重子 。
  • 轻子:跟重子相反,一个点不是重子就是轻子(叶子节点除外),对于每一个非叶子节点,它的子中非重儿子的剩下所有儿子即为轻子。
  • 重边:一个父亲连接他的重子的边称为重边。
  • 轻边:跟重边相反,每一条边不是重边就是轻边。
  • 重链:重链的要求非常难,一点是相邻的重边连起来一个重子的链就是重链。
    如果有还不理解的同学,我们来画一张图来找一找这些东西在哪里。请添加图片描述

需要的函数

第一个dfs

这个dfs他到底要解决什么问题呢?其实有这么用处。

  • 标记每一个点的深度
  • 预处理出每一个点的父亲
  • 标记每个非叶子节点的子树大小(含它自己)
  • 标记每个非叶子节点的重儿子编号son[]
void dfs1(int x,int f,int deep)
{//x当前节点,f父亲,deep深度 
	dep[x]=deep;//标记每个点的深度 
	fa[x]=f;//标记每个点的父亲 
	siz[x]=1;//标记每个非叶子节点的子树大小 
	int maxn=-1;//记录重儿子的儿子数 
	for(int i=beg[x];i;i=nex[i])
	{
		int y=to[i];
		if(y==f)
			continue;//若为父亲则continue 
		dfs1(y,x,deep+1);//dfs其儿子 
		siz[x]+=siz[y];//把它的儿子数加到它身上 
		if(siz[y]>maxn)
			son[x]=y,maxn=siz[y];//标记每个非叶子节点的重儿子编号 
	}
}
第二个dfs

第二个dfs的目的其实跟第一个是差不多的,同样是预处理,我们先列出来看看。

  • 标记每个点的新编号
  • 赋值每个点的初始值到新编号上
  • 处理每个点所在链的顶端
  • 处理每条链
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(int i=beg[x];i;i=nex[i])
	{
		int y=to[i];
		if(y==fa[x]||y==son[x])
			continue;
		dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
	}
}
树剖部分

敲一下黑板,这是最重要的部分,因为第二个dfs的便利顺序是先重子,再轻子,然后就不难得到一个很有用的东西:

  • 因为顺序是先重再轻,所以每一条重链的新编号是连续的
  • 因为是dfs,所以每一个子树的新编号也是连续的
    然后我们搬出题目让我们干的事情,一件一件分析:
  • 处理任意两点间路径上的点权和
  • 处理一点及其子树的点权和
  • 修改任意两点间路径上的点权
  • 修改一点及其子树的点权
  1. 处理任意两点间路径时:
    设所在链顶端的深度更深的那个点为 x x x a n s ans ans 加上 x x x 点到 x x x 所在链顶端 这一段区间的点权和,把 x x x 跳到 x x x 所在链顶端的那个点的上面一个点。可能会有一点点抽象,我们画一个图帮助理解一下。请添加图片描述
    那么好的,在分析一下,因为这先序号都是连续的,所以怎么维护呢,可以使用线段树,不会的可以翻往期的笔记。时间复杂度就是 O ( log ⁡ 2 n ) O\left(\log^2n\right) O(log2n)
inline int qRange(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){//当两个点不在同一条链上 
		if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
		ans+=res;
		ans%=mod;//按题意取模 
		x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
	}
	//直到两个点处于一条链上
	if(dep[x]>dep[y])
		swap(x,y);//把x点深度更深的那个点
	res=0;
	query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
	ans+=res;
	return ans%mod;
}
  1. 处理一点及其子树的点权和:想到记录了每个非叶子节点的子树大小(含它自己),并且每个子树的新编号都是连续的,直接线段树区间查询即可,时间复杂度 O ( log ⁡ n ) O\left(\log n\right) O(logn)
inline int qSon(int x){
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 
	return res;
}

下面是区间修改的,跟查询基本一样:

inline void updRange(int x,int y,int k){
	k%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}

inline void updSon(int x,int k){
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
建树

普通的线段树,不多说了。

代码部分

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
const int maxn=200000+10;
int n,m,r,mod;//见题意 
int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];//链式前向星数组,w、wt初始点权数组 
int a[maxn<<2],laz[maxn<<2];//线段树数组、lazy懒标记 
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn];//son重子编号,id新编号,fa父亲节点,cnt dfs_clock/dfs序,dep深度,siz子树大小,top当前链顶端节点
int res;
void add(int x,int y){//链式前向星加边 
    to[++e]=y;
    nex[e]=beg[x];
    beg[x]=e;
}
//线段树操作,不多说了,看往期笔记 
void pushdown(int rt,int lenn)
{
	laz[rt<<1]+=laz[rt];
	laz[rt<<1|1]+=laz[rt];
    a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
    a[rt<<1|1]+=laz[rt]*(lenn>>1);
    a[rt<<1]%=mod;
    a[rt<<1|1]%=mod;
    laz[rt]=0;
}
void build(int rt,int l,int r)
{
    if(l==r){
        a[rt]=wt[l];
        if(a[rt]>mod)a[rt]%=mod;
        return;
    }
    build(lson);
    build(rson);
    a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}
void query(int rt,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		res+=a[rt];
		res%=mod;
		return;
	}
	else
	{
		if(laz[rt])
			pushdown(rt,len);
		if(L<=mid)
			query(lson,L,R);
		if(R>mid)
			query(rson,L,R);
	}
}
void update(int rt,int l,int r,int L,int R,int k)
{
    if(L<=l&&r<=R)
	{
		laz[rt]+=k;
		a[rt]+=k*len;
	}
	else
	{
		if(laz[rt])
			pushdown(rt,len);
		if(L<=mid)
			update(lson,L,R,k);
		if(R>mid)
			update(rson,L,R,k);
		a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
	}
} 
int qRange(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])//当两个点不在同一条链上
	{ 
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);//把x点改为所在链顶端的深度更深的那个点
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
		ans+=res;
		ans%=mod;//按题意取模 
		x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
	}
	//直到两个点处于一条链上,跟LCA类似 
	if(dep[x]>dep[y])
		swap(x,y);//把x点深度更深的那个点
	res=0;
	query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
	ans+=res;
	return ans%mod;
}
void updRange(int x,int y,int k)//同上 
{
	k%=mod;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
int qSon(int x)
{
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 
	return res;
}
void updSon(int x,int k){//同上 
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
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=beg[x];i;i=nex[i])
	{
		int y=to[i];
		if(y==f)
			continue;//若为父亲则continue 
		dfs1(y,x,deep+1);//dfs其儿子 
		siz[x]+=siz[y];//把它的儿子数加到它身上 
		if(siz[y]>maxson)
			son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 
	}
}

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(int i=beg[x];i;i=nex[i])
	{
		int y=to[i];
		if(y==fa[x]||y==son[x])
			continue;
		dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
	}
}
int main()
{
    cin>>n>>m>>r>>mod; 
    for(int i=1;i<=n;i++)
		cin>>w[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        add(u,v);
		add(v,u);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
	while(m--)
	{
        int k,x,y,z;
        cin>>k;
        if(k==1)
		{
            cin>>x>>y>>z;
            updRange(x,y,z);
        }
        else if(k==2)
		{
            cin>>x>>y;
            printf("%d\n",qRange(x,y));
        }
        else if(k==3)
		{
            cin>>x>>y;
            updSon(x,y);
        }
        else
		{
            cin>>x;
            printf("%d\n",qSon(x));
    	}
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DMA(hal库)

DMA&#xff08;直接存储器存取&#xff09; DMA&#xff08;Direct Memory Access&#xff0c;直接内存访问&#xff09;是一种允许外设或内存之间直接传输数据的技术&#xff0c;而无需 CPU 参与。这能显著提高数据传输效率并减少 CPU 的负担。 使用 DMA 通常涉及以下步骤&…

图片转PDF怎么转?教你3种快捷方便的jpg转pdf方法

图片文件以及PDF文档已经是我们工作当中不可或缺的一部分&#xff0c;我们在一些商务合作的场景下经常需要把拍摄下来的合同、企划书、画册等图片内容转换为PDF格式后再发送&#xff0c;这样能够极大程度的保证文件的安全性&#xff0c;那么图片应该如何转换成PDF文件呢?今天来…

如何制作统信UOS启动盘?

如何制作统信UOS启动盘&#xff1f; 一、下载UOS系统安装镜像二、在UOS系统环境下制作启动盘步骤一&#xff1a;准备U盘步骤二&#xff1a;打开启动盘制作工具步骤三&#xff1a;选择ISO镜像文件步骤四&#xff1a;选择安装介质并格式化步骤五&#xff1a;等待制作完成 三、在W…

(LLM) 很笨

大型语言模型 (LLM) 并非你所想的那样。你被骗了。LLM 很笨&#xff0c;非常笨。事实上&#xff0c;它们更接近数据库&#xff0c;而不是人类。 这就是为什么人工智能仍然处于征服智能的第一步……如果有的话。 终极煤气灯效应 建立前沿人工智能的成本很高。前沿人工智能需要大…

Stable Diffusion 使用详解(8)--- layer diffsuion

背景 layer diffusion 重点在 layer&#xff0c;顾名思义&#xff0c;就是分图层的概念&#xff0c;用过ps 的朋友再熟悉不过了。没使用过的&#xff0c;也没关系&#xff0c;其实很简单&#xff0c;本质就是各图层自身的编辑不会影响其他图层&#xff0c;这好比OS中运行了很多…

10月天津人工智能主题——第三届人工智能与智能信息处理国际学术会议(AIIIP 2024)

【ACM出版 | IEEE&ACM院士、CCF杰出会员担任组委| 往届会后3个半月检索 】 第三届人工智能与智能信息处理国际学术会议&#xff08;AIIIP 2024&#xff09; 2024 3rd International Conference on Artificial Intelligence and Intelligent Information Processing 中国…

无人机飞手培训:考证、组装、维修技术详解

随着无人机技术的飞速发展&#xff0c;无人机已广泛应用于航拍、农业、环境监测、救援等多个领域&#xff0c;成为现代社会不可或缺的工具之一。作为无人机操作的核心——无人机飞手&#xff0c;其专业技能的掌握至关重要。本文档将详细解析无人机飞手培训的关键环节&#xff0…

关于Python的20个奇技淫巧

Python有非常多有趣使用的技巧&#xff0c;下面列举20个短小精炼的用法&#xff0c;其中既包含常规语法&#xff0c;又有第三方库的妙用&#xff0c;体现了python简单即美的编程哲学。 快速实现字频统计 from collections import Counterwords 我明白你的意思&#xff0c;你…

python将字典数据保存为json文件

目录 一、json库介绍 二、字典生成json文件 1、导入 json 模块 2、将字典数据保存为 json 文件 (1) 创建一个python字典 (2) 指定要保存的 json 文件路径 (3) 将字典数据存为 json 文件 3、读取 json文件&#xff0c;并打印 一、json库介绍 方法作用json.dumps()将py…

[java][mybatis]generatorConfig.xml配置信息详细

generatorConfig.xml配置信息详细 mybatis-generator有三种用法&#xff1a;命令行、eclipse插件、maven插件。个人觉得maven插件最方便&#xff0c;可以在eclipse/intellij idea等ide上可以通用。 下面是从官网上的截图&#xff1a; 在MBG中&#xff0c;最主要也最重要的就是…

拒绝拖延!Kimi助你一天内速成论文初稿!

撰写学术论文是一项需要周密计划和精确执行的任务。它要求作者对文章的每个部分进行深入思考&#xff0c;以确保论文结构的合理性和论述的清晰度。利用Kimi的功能&#xff0c;我们可以更系统地进行写作&#xff0c;从构思到最终成稿&#xff0c;逐步构建出一篇高质量的学术论文…

短视频SDK解决方案,高效集成,助力商业变现

美摄科技&#xff0c;作为业界领先的多媒体技术服务商&#xff0c;其全面升级的短视频SDK解决方案&#xff0c;旨在为开发者与内容创作者提供一站式、高效能的创作工具&#xff0c;让每一个灵感都能瞬间转化为触动人心的视频作品。 【一站式解决方案&#xff0c;重塑短视频创作…

外包出来,面试5次全挂,心态蹦了......

大概介绍一下个人情况&#xff0c;男&#xff0c;毕业于普通二本院校非计算机专业&#xff0c;18年跨专业入行测试&#xff0c;第一份工作在湖南某软件公司&#xff0c;做了接近4年的外包测试工程师&#xff0c;今年年初&#xff0c;感觉自己不能够再这样下去了&#xff0c;长时…

Web漏洞介绍和Sql注入漏洞

常规渗透测试流程 漏洞探测包括&#xff1a;web漏洞测试&#xff0c;系统漏洞测试 工具探测、手工验证 优先找的漏洞&#xff1a;中间&#xff08;件&#xff09;漏洞、框架漏洞...webserver 知道创宇 Seebug 漏洞平台 - 洞悉漏洞&#xff0c;让你掌握前沿漏洞情报&#xff…

API容易被攻击,如何做好API安全

随着互联网技术的飞速发展和普及&#xff0c;网络安全问题日益严峻&#xff0c;API&#xff08;应用程序接口&#xff09;已成为网络攻击的常见载体之一。API作为不同系统之间数据传输的桥梁&#xff0c;其安全性直接影响到整个系统的稳定性和数据的安全性。 根据Imperva发布的…

docker-harbor私有仓库部署和管理

harbor&#xff1a;开源的企业级的docker仓库软件 仓库&#xff1a;私有仓库 公有仓库 &#xff08;公司内部一般都是私有仓库&#xff09; habor 是有图形化的&#xff0c;页面UI展示的一个工具&#xff0c;操作起来很直观。 harbor每个组件都是由容器构建的&#xff0c;所…

CocosCreator3.8 IOS 构建插屏无法去除的解决方案

CocosCreator3.8 IOS 构建插屏无法去除的解决方案 在实际项目开发过程中&#xff0c;我们通常无需CocosCreator 自带的插屏&#xff0c;一般采用自定义加载页面。 然后在构建IOS 项目时&#xff0c;启用&#xff08;禁用&#xff09;插屏无法操作&#xff0c;如下图所示&#…

PDF转图片神器!一键转换,告别繁琐操作

自从出了社会以来&#xff0c;不论是在职场应对工作还是日常的信息传输等等场景都是经常需要用到各类格式之间转换的情况&#xff0c;其中尤其是pdf和图片这类编辑条件要求偏高的工具对信息的保密程度更高&#xff0c;所以往往也是出现频率比较高的格式&#xff0c;今天针对pdf…

heic图片转换成jpg怎么快速转换?5个软件手把手教你转换图片片格式

heic图片转换成jpg怎么快速转换&#xff1f;5个软件教你轻松转换图片格式 将HEIC格式的图片转换为更常见的JPG格式可能是许多人在使用苹果设备后常遇到的需求。以下是五款能够帮助你快速完成HEIC转JPG的实用软件&#xff0c;无论是在线工具还是桌面应用&#xff0c;都可以让你…

普元EOS-服务端获取当前登录用户信息

1 前言 EOS服务端需要获取当前登录人员的信息&#xff0c;获取方法如下&#xff1a; import com.eos.data.datacontext.DataContextManager; import com.eos.data.datacontext.IUserObject; import com.eos.data.datacontext.UserObject;UserObject userObj (UserObject) Da…