博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
树链剖分学习笔记
阅读量:5092 次
发布时间:2019-06-13

本文共 2384 字,大约阅读时间需要 7 分钟。

 毕竟刚学到了皮毛,还不足以讲,如果想较为深入的学习的话,请观摩博客:

更博啦。经过这几天的树链剖分学习,感觉还不是那么难,所以就写一下笔记,供大家分享。这份博客就用洛谷的一道模板题来讲

学习之前必须要掌握一些知识,不然你看几天都不会看得懂:线段树,dfs序。

如果掌握了就来看一下这几个定义

  • 重结点:子树结点数目最多的结点;
  • 轻节点:父亲节点中除了重结点以外的结点;
  • 重边:父亲结点和重结点连成的边;
  • 轻边:父亲节点和轻节点连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

定义没有什么好说的,我们就开始正式讲树链剖分。

树链剖分

比如上面这幅图中,用黑线连接的结点都是重结点,其余均是轻结点,2-11、1-11就是重链,其他就是轻链,用红点标记的就是该结点所在链的起点,也就是我们?提到的top结点,还有每条边的值其实是进行dfs时的执行序号。

然后就是实现这个算法的辅助数组

名称 解释
size[x]     保存以x为根的子树节点个数
top[x] 保存当前节点所在链的顶端节点
son[x] 保存重儿子
deep[x]        保存结点x的深度值
fa[x] 保存结点u的父亲节点
id[x] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)

 

 

 

 

 

 

 

 

 

 

此外还有两条性质

1.如果(u,v)是轻边,那么size[v]<size[u]/2

2.从根结点到任意结点的路所经过的轻重链的个数必定都小与O(logn);

然后我们要对这棵树进行预处理

我们用到了两个dfs,dfs1是处理子树大小和父子关系还有节点深度的,dfs2是用来处理编号和重链以及中儿子的。

然后这两个dfs比较好理解,这里就只粘代码,代码里面有注释,可以感性理解一下。

void dfs1(int x,int f){    fa[x]=f;//首先记录父亲     size[x]=1;    for(int i=head[x]; i!=-1; i=e[i].next)    {        if(!deep[e[i].to] && e[i].to != f)        {            deep[e[i].to]=deep[x]+1;//深度             dfs1(e[i].to,x);            size[x]+=size[e[i].to];//子树大小             if(size[e[i].to]>size[son[x]]) son[x]=e[i].to;//如果当前子树比中儿子的子树大,那么当前子树的根为重儿子         }    }}
dfs1
void dfs2(int x,int topf)//topf为当前重链的顶部 {    idx[x] = ++cnt;//编号     top[x] = topf;    if(!son[x]) return;//小优化     dfs2(son[x],topf);    for(int i=head[x]; i!=-1; i=e[i].next)        if(!idx[e[i].to] && e[i].to!=fa[x]) dfs2(e[i].to,e[i].to);}
dfs2

理解了两个dfs,我们就好进行了!

其余的事情就是线段树做的事了,区间加和,区间查询,pushdown操作一样。但是树链剖分还有几个其他的操作。

  1. 节点路径修改
  2. 节点路径查询和
  3. 子树修改
  4. 子树查询
  5. 换根

节点路径修改这个操作需要用到一个函数:树上修改(change_tree)

这个思想就是用到了倍增,这样考虑,因为两个节点分别在两条重链上,然后你不断缩短重链,期间不断进行区间修改,然后这两个节点最后一定在一个重链上,在进行最后一步区间修改,大功告成。树上查询也是这样的。然后就是这段代码。

void add_tree(int x,int y,int val){    while(top[x]!=top[y])    {        if(deep[top[x]]
树上修改
int asksum_tree(int x,int y){    int ans=0;    while(top[x]!=top[y])    {        if(deep[top[x]]
树上查询

子树修改和子树查询也非常的简单。你可以观察一下发现,因为树映射到线段树上的是一段区间,所以它的子树的区间为[id[x],id[x]+size[x]-1],所以代码如下

//子树修改change_interval(1,id[x],id[x]+size[x]-1,val)//子树查询ask_interval(1,id[x],id[x]+size[x]-1)
子树相关

 之后就是我所说的换根操作。这个操作看似很麻烦,其实不然,我们可以用分类讨论的思想:设查询点为a,当前根节点为root

  1. 查询点与根节点相同:直接返回根节点的值
  2. LCA(a,root)!=a 那么直接返回a的子树的信息
  3. LCA(a,root)==a ,所以a和root在一棵子树,因为这棵树对应在线段树上的区间是连续的,所以我们访问除root子树之外的所有的区间,就是查询a的子树[1,id[root]-1]&[id[root]+size[x],n]

有些题有可能你会碰上树剖求LCA,这里就奉上树剖求LCA代码,代码很简单,你一定能看懂,而且你会发现耗时惊的一批

int LCA(int x,int y){    while(top[x]!=top[y])    {        if(deep[top[x]]
树剖求LCA

 

转载于:https://www.cnblogs.com/ifmyt/p/9342038.html

你可能感兴趣的文章
HDU1024 Max Sum Plus Plus 【DP】
查看>>
[你必须知道的.NET]第二十一回:认识全面的null
查看>>
十六进制的ASCII码 "\u6cf0\u56fd" 解码成unicode
查看>>
Java语言概述
查看>>
关于BOM知识的整理
查看>>
android中自定义下拉框(转)
查看>>
Android设计模式源码解析之外观模式(Facade)
查看>>
使用word发布博客
查看>>
构建oracle12c的Docker镜像
查看>>
用户权限命令(chmod,chown,umask,lsattr/chattr)
查看>>
Maven详解
查看>>
Linux系统中‘dmesg’命令处理故障和收集系统信息的7种用法
查看>>
数据结构 : Hash Table [II]
查看>>
面向对象的小demo
查看>>
获取地址栏参数
查看>>
java之hibernate之helloworld
查看>>
微服务之初了解(一)
查看>>
Iterator invalidation(迭代器失效)
查看>>
GDOI DAY1游记
查看>>
网络流24题(更新中
查看>>