树链剖分之重链剖分详解

  • 一些概念
  • 算法讲解
  • 应用
    • 求最近公共祖先
    • 对树上的一条链进行修改和查询
  • 相关练习题

一些概念

在学习重链剖分前,首先要明白以下几个概念:

  1. 中二重儿子:就是一个节点的儿子中最“重”的那个,“重”表示的是子树大小最大,如果都一样大,就随便选一个就好了(用 s o n son son数组存储)
  2. 亲轻儿子:除了重儿子外其他的儿子
  3. 重边:重儿子和父亲之间的边
  4. 轻边:轻儿子和父亲之间的边
  5. 重链:重边连在一起形成的链
  6. 轻链:轻边连在一起形成的链(貌似没啥用)
  7. 重链顶点:一条重链中,深度最小的点(用 t o p top top数组记录)

为了方便大家理解,这里我画了一张图,来表示重链剖分后的结果:

其中,红色的线表示重边,连在一起,形成 3 3 3条重链;黑色的线表示轻边,连在一起,形成 3 3 3条轻链
这样,大家对这些概念应该都了解了吧?

算法讲解

接下来,我们来介绍如何进行重链剖分
首先,我们进行第一遍dfs,求出子树的大小( s i z siz siz),重儿子( s o n son son),父亲节点( f a fa fa)和每个点的深度( d e p dep dep)
s i z e size size、 f a fa fa和 d e p dep dep的求法就不用说了吧……
我们来讲讲 s o n son son的求法
首先,设一个变量 m a x s o n maxson maxson,初值为 − 1 -1 −1,记录最大的子树大小
接着,对于枚举的每棵子树,假设当前枚举的子树的根为 v v v,判断 m a x s o n maxson maxson和 s i z [ v ] siz[v] siz[v]的大小
如果 s i z [ v ] > m a x s o n siz[v]>maxson siz[v]>maxson,那么 s o n [ u ] = v son[u]=v son[u]=v
代码:

int dfs1(int x,int Fa,int Dep)
{dep[x]=Dep,fa[x]=Fa,siz[x]=1;int maxson=-1;for(int i=head[x];i!=-1;i=e[i].nxt) if(e[i].v!=Fa){siz[x]+=dfs1(e[i].v,x,Dep+1);if(siz[e[i].v]>maxson) maxson=siz[e[i].v],son[x]=e[i].v;}return siz[x];
}

接着,我们来讲一讲第二个dfs,这个dfs要求出dfs序( d f n dfn dfn)和重链顶点( t o p top top)
为了方便,我们首先先遍历重儿子,再遍历其他轻儿子,重儿子的重链顶点和该节点的重链顶点相同,轻儿子的重链顶点是自己
至于方便在什么地方,后面再说
代码:

void dfs2(int x,int Top)
{dfn[x]=++cnt,top[x]=Top;if(!son[x]) return;dfs2(son[x],Top);for(int i=head[x];i!=-1;i=e[i].nxt) if(!dfn[e[i].v]) dfs2(e[i].v,e[i].v);
}

这样,我们就完成了重链剖分的全过程

应用

那么,剖分完后又有什么好处呢?

求最近公共祖先

~~众所周知,~~有一种求最近公共祖先(LCA)的算法,使用的是倍增,这个算法预处理是 Θ ( n l o g n ) \Theta(nlogn) Θ(nlogn)的,单次询问是 Θ ( l o g n ) \Theta(logn) Θ(logn)的
有比这个更快的算法吗?有!用重链剖分解决!
等等,这和重链剖分有什么关系呢?最近公共祖先和这两个点所在的重链也不一定一样啊,这怎么做呢?
别急,马上大家就会理解了
我们以下面这张图为例,求两个蓝色的点的最近公共祖先

模仿倍增法求LCA,我们就很容易理解重链剖分求LCA的方法了
我们只需要选择深度较深的点,不断向上跳到重链的顶端即可
首先,蓝色的点调到重链顶端(绿点),然后再向上跳一条边(黄点),这样就转换到了另外一条重链上
最后,当两个点在同一条重链上时,深度较小的那个点就是他们的最近公共祖先了
那么,重链剖分求LCA时,预处理为 Θ ( n ) \Theta(n) Θ(n),单次询问为 Θ ( l o g n ) \Theta(logn) Θ(logn)
完整代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct ppap
{int u,v,nxt;
}e[2000010];
int n,m,root,tot,cnt,head[2000010],dep[2000010],fa[2000010],son[2000010],siz[2000010],top[2000010],dfn[2000010];
void add(int x,int y)
{e[++tot].nxt=head[x],head[x]=tot,e[tot].u=x,e[tot].v=y;
}
int dfs1(int x,int Fa,int Dep)
{dep[x]=Dep,fa[x]=Fa,siz[x]=1;int maxson=-1;for(int i=head[x];i!=-1;i=e[i].nxt) if(e[i].v!=Fa){siz[x]+=dfs1(e[i].v,x,Dep+1);if(siz[e[i].v]>maxson) maxson=siz[e[i].v],son[x]=e[i].v;}return siz[x];
}
void dfs2(int x,int Top)
{dfn[x]=++cnt,top[x]=Top;if(!son[x]) return;dfs2(son[x],Top);for(int i=head[x];i!=-1;i=e[i].nxt) if(!dfn[e[i].v]) dfs2(e[i].v,e[i].v);
}
int LCA(int x,int y)
{while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);x=fa[top[x]];}if(dep[x]>dep[y]) return y;return x;
}
int main()
{memset(head,-1,sizeof(head));cin>>n>>m>>root;for(int i=1,x,y;i<=n-1;i++) cin>>x>>y,add(x,y),add(y,x);dfs1(root,0,1),dfs2(root,root);while(m--){int x,y;cin>>x>>y;cout<<LCA(x,y)<<endl;}
}

对树上的一条链进行修改和查询

重链剖分还有什么作用呢?
它可以对树上的任意一条链进行修改
以下图为例,我们假设每个点有一个点权,我们要让两个蓝点之间的链上的每个点的权 + 1 +1 +1(包括两个蓝点),怎么操作呢?

首先,对于这种相连的东西一起修改的题目,我们很容易就会想到线段树
在树上建线段树,用的一定就是dfs序了
还记得我们前面遍历每个节点的子节点时,是先遍历它的重儿子吗?
当时我说这是为了方便
是的,就是在建线段树和计算时更方便了!
因为所有重链上的点的dfs序一定是连续的,这样,一条重链上的点的值修改只需要进行简单的线段树上区间修改即可
所以我们考虑把链划分成许多个重链相连(如下图)

那么,我们就可以分别修改每一段的值,查询时也一样,分别查询每一段的值,然后再加在一起即可
对于这个链的划分操作,和LCA的求法类似,只需要不断地跳到重链顶点的父亲节点,就可以了
模板题代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct ppap
{int u,v,nxt;
}e[2000010];
struct Segment_Tree
{int l,r,sum,siz,add;
}t[2000010];
int n,m,root,MOD,tot,cnt,a[2000010],b[2000010],head[2000010],dep[2000010],fa[2000010],son[2000010],siz[2000010],top[2000010],dfn[2000010];
void add(int x,int y)
{e[++tot].nxt=head[x],head[x]=tot,e[tot].u=x,e[tot].v=y;
}
int dfs1(int x,int Fa,int Dep)
{dep[x]=Dep,fa[x]=Fa,siz[x]=1;int maxson=-1;for(int i=head[x];i!=-1;i=e[i].nxt) if(e[i].v!=Fa){siz[x]+=dfs1(e[i].v,x,Dep+1);if(siz[e[i].v]>maxson) maxson=siz[e[i].v],son[x]=e[i].v;}return siz[x];
}
void dfs2(int x,int Top)
{dfn[x]=++cnt,a[cnt]=b[x],top[x]=Top;if(!son[x]) return;dfs2(son[x],Top);for(int i=head[x];i!=-1;i=e[i].nxt) if(!dfn[e[i].v]) dfs2(e[i].v,e[i].v);
}
void build(int p,int l,int r)
{t[p].l=l,t[p].r=r,t[p].siz=r-l+1;if(l==r){t[p].sum=a[l];return;}int mid=(l+r)>>1;build(p*2,l,mid);build(p*2+1,mid+1,r);t[p].sum=(t[p*2].sum+t[p*2+1].sum+MOD)%MOD;
}
void spread(int p)
{if(t[p].add){t[p*2].sum=(t[p*2].sum+t[p*2].siz*t[p].add)%MOD;t[p*2+1].sum=(t[p*2+1].sum+t[p*2+1].siz*t[p].add)%MOD;t[p*2].add=(t[p*2].add+t[p].add)%MOD;t[p*2+1].add=(t[p*2+1].add+t[p].add)%MOD;t[p].add=0;}
}
void change(int p,int l,int r,int d)
{if(l<=t[p].l&&t[p].r<=r){t[p].sum+=t[p].siz*d,t[p].add+=d;return;}spread(p);int mid=(t[p].l+t[p].r)>>1;if(l<=mid) change(p*2,l,r,d);if(r>mid) change(p*2+1,l,r,d);t[p].sum=(t[p*2].sum+t[p*2+1].sum+MOD)%MOD;
}
int ask(int p,int l,int r)
{int ans=0;if(l<=t[p].l&&t[p].r<=r) return t[p].sum;spread(p);int mid=(t[p].l+t[p].r)>>1;if(l<=mid) ans=(ans+ask(p*2,l,r))%MOD;if(r>mid)  ans=(ans+ask(p*2+1,l,r))%MOD;return ans;
}
void modify(int x,int y,int d)
{while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);change(1,dfn[top[x]],dfn[x],d);x=fa[top[x]];}if(dep[x]>dep[y]) swap(x,y);change(1,dfn[x],dfn[y],d);
}
void query(int x,int y)
{int ans=0;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);ans=(ans+ask(1,dfn[top[x]],dfn[x]))%MOD;x=fa[top[x]];}if(dep[x]>dep[y]) swap(x,y);ans=(ans+ask(1,dfn[x],dfn[y]))%MOD;cout<<ans<<endl;
}
int main()
{memset(head,-1,sizeof(head));cin>>n>>m>>root>>MOD;for(int i=1;i<=n;i++) cin>>b[i];for(int i=1,x,y;i<=n-1;i++) cin>>x>>y,add(x,y),add(y,x);dfs1(root,0,1),dfs2(root,root),build(1,1,n);while(m--){int op,x,y,z;cin>>op;if(op==1) cin>>x>>y>>z,z%=MOD,modify(x,y,z);else if(op==2) cin>>x>>y,query(x,y);else if(op==3) cin>>x>>z,change(1,dfn[x],dfn[x]+siz[x]-1,z%MOD);else if(op==4) cin>>x,cout<<ask(1,dfn[x],dfn[x]+siz[x]-1)<<endl;}
}

相关练习题

  1. 洛谷P4315 月下“毛景树”
  2. 洛谷P2146 [NOI2015]软件包管理器 题解
  3. 洛谷P4949 最短距离
  4. 其他需要用到LCA的题目,都可以使用重链剖分求,这里就不列举了

树链剖分之重链剖分详解相关推荐

  1. 对LCA、树上倍增、树链剖分(重链剖分长链剖分)和LCT(Link-Cut Tree)的学习

    LCA what is LCA & what can LCA do LCA(Lowest Common Ancestors),即最近公共祖先 在一棵树上,两个节点的深度最浅的公共祖先就是 L ...

  2. 树链剖分(轻重链剖分)

    树链剖分,是一种将树剖分为链,在链上进行各种操作以达到目的的算法.树链剖分(轻重链剖分)可以解决lca(最近公共祖先),树上操作(对树上两点及经过的路的权值进行求和,修改等操作)等一类操作.对于这些问 ...

  3. 目录树 删除 数据结构_数据结构:B树和B+树的插入、删除图文详解

    B树 1.1B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数.当m取2时,就是我们常见的二叉搜索树. ...

  4. 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  5. 数据结构:B树和B+树的插入、删除图文详解

    目录 B树 1.1B树的定义 1.2B树的插入操作 1.3B树的删除操作 B+树 2.1 B+树的定义 2.2 B+树的插入操作 2.3 B+树的删除操作 B树 1.1B树的定义 B树也称B-树,它是 ...

  6. JMeter学习(五)---察看结果树和聚合报告指标项详解

    在用JMeter做测试过程中,用到最多的监听器就是察看结果树和聚合报告啦,那取样器中的各项指标代表什么含义呢?我觉得了解一下还是很有必要的,有助于我们观察和分析结果.下面开始介绍: 察看结果树 界面如 ...

  7. 区块链用哪种语言 Java_区块链开发用什么语言呢?区块链与编程语言的关系详解...

    原标题:区块链开发用什么语言呢?区块链与编程语言的关系详解 区块链的概念就随着比特币的大热开始逐渐进入公众视野,比特币的拥有者为了使其创造更高的财富,开始疯狂炒作,因而引发购买比特币大浪潮.然而等到比 ...

  8. 玉米社:外链、反链、内链、友链的区别与联系详解

    外链.反链.内链.友链的区别与联系详解 1.外链 外链指从站外链向本站点某页面的链接,主要强调站外.我们常说的发外链,即在外部博客.论坛.自媒体等平台发布的带有自身网站链接的软文或者视频内容,但凡是从 ...

  9. AVL树的插入与删除(详解)

    AVL树的插入与删除(详解) 平衡二叉树的定义就不在这里赘述了,平衡二叉树的插入与删除都是基于平衡二叉树的查找进行的.平衡二叉树的查找和二叉树的查找又是一样的. 插入的话,我们从平衡二叉树的根结点出发 ...

最新文章

  1. 手把手教你使用zabbix监控nginx
  2. 漫画:据说很多搞软件的羡慕硬件工程师
  3. java 使用new新建一个对象时的操作过程
  4. Harbor Docker 镜像仓库搭建
  5. python空气质量分析报告_Python数据可视化:2018年北上广深空气质量分析
  6. 如何修改Vue打包后文件的接口地址配置
  7. Echarts动态加载地图数据(Dynamic load Echarts map data)
  8. linux shell脚本关于文件存在与否的判断
  9. clion中自定义消息msg消息时定义的msg文件有类型提示
  10. python没基础能自学吗-没有基础先要自学python,有什么比较好的书推荐?
  11. Linux串口终端驱动——S3C6410平台
  12. Matlab将散点绘制为平滑曲线的简单方法
  13. MindMeld中文文档--2.构建会话应用程序的不同方法[Different Approaches for Building Conversational Applications]
  14. spark处理大数据实例
  15. 有线网与无线网(WIFI)网速的限制因素与Wifi信道选择
  16. 显示硬件发展与视频开发系列(6)----显示标注与视频处理单元(4):GPGPU
  17. 论文阅读Check it again:Progressive Visual Question Answering via Visual Entailment
  18. excel矩阵小tips
  19. 爬虫练习(3)-- 36Kr 站点正则和 ajax 混合
  20. 这篇 ElasticSearch 详细使用教程,内部分享时被老大表扬了

热门文章

  1. 怎么更改计算机物理地址,win7如何改物理地址_win7电脑怎么更改物理地址
  2. 贝叶斯统计推断(点估计和区间估计)
  3. JAVA入门[20]-Spring Data JPA简单示例
  4. 前端 | (二)各种各样的常用标签 | 尚硅谷前端html+css零基础教程2023最新
  5. 【板栗糖GIS】BAT—win11恢复经典win10右键内容
  6. 数字排大小C语言怎么编写,C语言排列数字大小
  7. BUU-Crypto-萌萌哒的八戒
  8. 日语助词に的所有的语法点,请牢记
  9. 阿里云服务器处理挖矿程序过程
  10. 中北和山大哪个计算机专业好点,山大和中北竞选双一流,山大综合实力更强,为何中北却获更多支持...