树链剖分
2016-03-23 14:29
288 查看
这几天学习了一下树链剖分,个人感觉是线段树在树上的应用,也不能算是一种数据结构吧。比较常见的是对树边的修改和查询问题。
问题引入:
如果题目是在一棵树上进行路径的修改、求极值、求和等问题,大家很容易就会想到用线段树来做,但是这就存在一个问题:线段树存储的是一段连续区间上的信息,,而树的结构是分散的,那么怎么把树的节点或者树边和这个区间联系起来呢?树链剖分就可以解决这个问题。
首先,我们来解读一下算法名称:
1、树链,就是树上的路径。
2、剖分,就是把路径分类为重链和轻链。
树链剖分用一句话概括就是:把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一条链以解决问题,算法复杂度为O(logn)。
这里需要几个数组来保存树的信息:
size[u] 表示以 u 为根的子树的节点数
dep[u] 表示 u 的深度(根深度为1)
top[u] 表示 u 所在的链的顶端节点
fa[u] 表示u的父亲节点
son[u] 表示与u在同一重链上的u的儿子节点(姑且称为重儿子)
id[u] 表示u与其父亲节点的连边(姑且称为u的父边)在线段树中的位置
rank[i] 表示线段树中第 i 个数对应树中的节点编号
涉及到的几个概念:
重儿子:size[v]为u的子节点中size值最大的,那么v就是u的重儿子。
轻儿子:u的其它子节点。
重边:点u与其重儿子的连边。
轻边:点u与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
剖分后的树有如下性质:
性质1:如果(u,v)为轻边,则size[v] * 2 < size[u];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
算法实现:
一、首先,我们可以对树进行两遍 dfs 求得以上数组的值
第一次dfs:求出 dep, size, fa, son,记录所有的重边
第二次dfs:连接重边成重链
二、然后,建立线段树。将树中的权值在线段树中更新,这样建链和建线段树就完成了。具体实现可以看相关题解。
三、最后,就是修改和查询操作。单点的修改和查询很简单,只要将对应线段树中位置的信息修改或查询即可。重点要讲的是区间上的修改和查询,这两个操作写法类似,以修改为例。
如何修改u到v的边权的值呢?这里有两种情况:
(1)如果u与v在同一条重链上,那么就直接修改了
(2)如果u与v不在同一条重链上,那么就一边进行修改,一边将u与v往同一条重链上靠,这样就变成了第一种情况了
这样问题的关键就变成了如何将 u 和 v 往同一条重链上靠。
具体解法如下:
我们记 tp1 = top[u],tp2 = top[v]
(1) 当tp1 != tp2 时:不妨设dep[tp1] >= dep[tp2],那么就更新u到tp1的父边的权值(logn),并使u = fa[tp1]。
(2)当tp1 = tp2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
重复上述过程,直到修改完成。
算法正确性证明:
首先我们需要深入理解一下dfs_2 。可以看出,dfs_2的核心是将树按一条条重链保存到线段树中。在树中同一条重链的节点或边的信息在线段树中是连续的,而且是深度小的保存在前面,深度大的保存在后面,这样线段树中保存的是一段段重链的信息。
如果需要修改 u 到 v 的信息,如果u和v不在同一条重链上,我们必定需要分别修改u 和 v 到其所在重链的头结点的信息,重复下去最终 u 到 v 的修改会演变成同一条重链上两个节点间的修改。算法证毕。
区间修改操作举例代码:
区间查询操作举例代码:
以上都是针对树边操作的树链剖分,也有对树的节点进行修改查询的题目,方法类似,只需要在理解的时候改变一下 id 数组的含义:id[ u ]表示 u 节点在线段树中的位置。
当然,如果是对节点的修改和查询,我们就可以用树状数组而不是线段树来保存信息,这样代码会简单很多。
举例:
hdu 3966 /article/5857307.html
问题引入:
如果题目是在一棵树上进行路径的修改、求极值、求和等问题,大家很容易就会想到用线段树来做,但是这就存在一个问题:线段树存储的是一段连续区间上的信息,,而树的结构是分散的,那么怎么把树的节点或者树边和这个区间联系起来呢?树链剖分就可以解决这个问题。
首先,我们来解读一下算法名称:
1、树链,就是树上的路径。
2、剖分,就是把路径分类为重链和轻链。
树链剖分用一句话概括就是:把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一条链以解决问题,算法复杂度为O(logn)。
这里需要几个数组来保存树的信息:
size[u] 表示以 u 为根的子树的节点数
dep[u] 表示 u 的深度(根深度为1)
top[u] 表示 u 所在的链的顶端节点
fa[u] 表示u的父亲节点
son[u] 表示与u在同一重链上的u的儿子节点(姑且称为重儿子)
id[u] 表示u与其父亲节点的连边(姑且称为u的父边)在线段树中的位置
rank[i] 表示线段树中第 i 个数对应树中的节点编号
涉及到的几个概念:
重儿子:size[v]为u的子节点中size值最大的,那么v就是u的重儿子。
轻儿子:u的其它子节点。
重边:点u与其重儿子的连边。
轻边:点u与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
剖分后的树有如下性质:
性质1:如果(u,v)为轻边,则size[v] * 2 < size[u];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
算法实现:
一、首先,我们可以对树进行两遍 dfs 求得以上数组的值
第一次dfs:求出 dep, size, fa, son,记录所有的重边
void dfs_1( int u, int f, int d) //主函数中调用dfs(1,0,1) { dep[u] = d; size[u] = 1; fa[u] = f; for(int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if(v == f) continue; dfs_1( v, u, d+1); size[u] += size[v]; if(son[u] == -1 || size[son[u]] < size[v]) son[u] = v; } }
第二次dfs:连接重边成重链
void dfs_2( int u, int tp) //主函数中调用dfs(1,1) { top[u] = tp; id[u] = ++tmp; rank[tmp] = u; if(son[u] != -1) dfs_2( son[u], tp); for(int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if(v != fa[u] && v != son[u]) dfs_2( v, v); } }
二、然后,建立线段树。将树中的权值在线段树中更新,这样建链和建线段树就完成了。具体实现可以看相关题解。
三、最后,就是修改和查询操作。单点的修改和查询很简单,只要将对应线段树中位置的信息修改或查询即可。重点要讲的是区间上的修改和查询,这两个操作写法类似,以修改为例。
如何修改u到v的边权的值呢?这里有两种情况:
(1)如果u与v在同一条重链上,那么就直接修改了
(2)如果u与v不在同一条重链上,那么就一边进行修改,一边将u与v往同一条重链上靠,这样就变成了第一种情况了
这样问题的关键就变成了如何将 u 和 v 往同一条重链上靠。
具体解法如下:
我们记 tp1 = top[u],tp2 = top[v]
(1) 当tp1 != tp2 时:不妨设dep[tp1] >= dep[tp2],那么就更新u到tp1的父边的权值(logn),并使u = fa[tp1]。
(2)当tp1 = tp2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
重复上述过程,直到修改完成。
算法正确性证明:
首先我们需要深入理解一下dfs_2 。可以看出,dfs_2的核心是将树按一条条重链保存到线段树中。在树中同一条重链的节点或边的信息在线段树中是连续的,而且是深度小的保存在前面,深度大的保存在后面,这样线段树中保存的是一段段重链的信息。
如果需要修改 u 到 v 的信息,如果u和v不在同一条重链上,我们必定需要分别修改u 和 v 到其所在重链的头结点的信息,重复下去最终 u 到 v 的修改会演变成同一条重链上两个节点间的修改。算法证毕。
区间修改操作举例代码:
void change(int u, int v, int value) { int tp1 = top[u], tp2 = top[v]; while(tp1 != tp2) { if(dep[tp1] < dep[tp2]) { swap( tp1, tp2); swap( u, v); } update(1, id[tp1], id[u], value); u = fa[tp1]; tp1 = top[u]; } if(u == v) return; if(dep[u] > dep[v]) swap( u, v); update(1, id[u], id[v], value); }
区间查询操作举例代码:
int find( int u, int v) { int tp1 = top[u], tp2 = top[v]; int res = 0; while(tp1 != tp2) { if(dep[tp1] < dep[tp2]) { swap( tp1, tp2); swap( u, v); } res = max(res, query( 1, id[tp1], id[u])); u = fa[tp1]; tp1 = top[u]; } if(u == v) return res; if(dep[u] > dep[v]) swap( u, v); res = max( res, query(1, id[son[u]], id[v])); return res; }
以上都是针对树边操作的树链剖分,也有对树的节点进行修改查询的题目,方法类似,只需要在理解的时候改变一下 id 数组的含义:id[ u ]表示 u 节点在线段树中的位置。
当然,如果是对节点的修改和查询,我们就可以用树状数组而不是线段树来保存信息,这样代码会简单很多。
举例:
hdu 3966 /article/5857307.html
相关文章推荐
- leetcode 321. Create Maximum Number
- 理解MySQL存储过程和函数
- String.split()结尾空字符串将被丢弃
- Fragment详解(二):Fragment 与支持库
- Go时间格式化和类型互换操作
- PowerDesigner建模经验
- 关于IOS我要学的d
- Jedis 操作
- wait与waitpid
- Tags Layers
- 25. Reverse Nodes in k-Group
- 最强正则表达式
- iOS 分类(category)、扩展(Extension)
- 306. Additive Number
- jquery鼠标悬停时的放大图片的图片提示效果
- NOIp 2010 导弹拦截
- JSON.parse()方法
- 队列—队列的存储实现及运算实现
- SharePoint2013 IT Professional - Permission
- Redis命令总结