一、认识树链剖分
首先感谢https://www.cnblogs.com/George1994/p/7821357.html,看这篇文章让我对树链剖分有了很好的了解。下面自我梳理下。
它是为了将树型数据按照一定的规则线性化变成一条链,然后在树上的操作便可以结合线段树或树状数组来维护原本在树上的数据。
规则是:让重儿子连成一条链,一棵树必定由多条重链组成。
几个概念:
1.首先就是一些必须知道的概念:
重结点:子树结点数目最多的结点;
轻节点:父亲节点中除了重结点以外的结点;
重边:父亲结点和重结点连成的边;
轻边:父亲节点和轻节点连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;
2.如下图对几个数组数据做说明
另在下方代码中还涉及到几个其它数组。
sz[i] // 保存i为根的子树节点个数。
son[i]//保存i的重儿子,即i的孩子为根的子树中拥有最多节点的那个孩子。
dept[i]//保存i的深度值,根节点深度是1.
fa[i]//保存i的父亲
3.算法原理
需要进行两次的DFS。第一次DFS,得到每个节点的父亲结点(fa数组)、当前结点的深度值(dept数组)、当前结点的子结点数量(sz数组)、当前结点的重结点(son数组).
void dfs1(int u,int father,int depth){
dept[u] = depth; //记录深度
fa[u] = father; //记录父亲节点
sz[u] = 1; //以u为根的子树节点数初始状态
for(int i= head[u];i ; i = edge[i].next){// 遍历所有和当前结点连接的结点
int v = edge[i].v;
int w = edge[i].w;
if(v != father ){
dis[v] = dis[u] + w; //此处是为了记v到根节点的距离
dfs1(v,u,depth+1);
sz[u] += sz[v];//累加孩子节点为根的子树节点数
if(sz[son[u]] < sz[v]){ //维护重儿子的节点编号
son[u] = v;
}
}
}
}
第二次DFS的时候则可以将各个重结点连接成重链,保证重链上节点连续编号,其实就是一段区间,用数据结构(一般是树状数组或线段树)来进行维护,为每个节点进行编号的过程,其实就是DFS在执行时的顺序(记录在tid数组),以及当前节点所在链的起点(tp数组),还有当前编号是树中的哪个节点(rnk数组)。
void dfs2(int u,int t){//t为u所在重链的起始节点编号
tp[u] = t; //记录u所在链的开始节点
tid[u] = tot; //设置当前结点的dfs执行序号
rnk[tot] = u; // 设置dfs序号对应成当前结点
tot++;
if(son[u] == 0) return ; //如果该节点无重孩子直接返回
dfs2(son[u],t);//从重儿子出发继续深搜
for(int i= head[u]; i; i= edge[i].next){
int v = edge[i].v;
if(v != son[u] && v != fa[u]){ //如果v不是重儿子也不是u的父亲,则v为新的重链的开始。
dfs2(v,v);
}
}
}
这样就将树结构进行了树链剖分成了多条重链。
二、利用树链剖分求lca
它与倍增的方式求LCA不同之处在于,往上爬的方式不同,其实质是让对应的点向上直接爬到根据tp数组中记录的重链头上去。
假设要求(x,y)两个点的最近公共祖先,树链剖分后的往上爬的算法过程是:
比较x,y的链头是否相等 。
如果相等,则x,y在同一条重链上,那么深度小的节点便解。
如果不相等,则比较二者链头的深度,让链头深度大的点往上爬,爬到链头的父亲结点处。继续重复比较x,y的链头是否相等。
如上图中求x = 2,y = 5两个节点的最近公共祖先;
过程是:
1.2的链头2与5的链头6不相等,比较各自链头2与6的深度,dept[2] > dept[6],那么让2走到4节点处,这时x = 4,y = 5。
2.4的链头1 与5的链头6不相等,比较各自链头1与6的深度,dept[1] < dept[6],那么让5走到链头6的父节点1处,这时x = 4,y = 1。
3.4的链头1 与1的链头1 相等,返回4与1中深度较小的点就是要求的解。
int lca(int x,int y){
while(1){
if(tp[x] == tp[y]){ //如果链头相等,则在一条重链上
return dept[x] <= dept[y]? x : y;
}
else if(dept[tp[x]] >= dept[tp[y]]){
x = fa[tp[x]];//爬到x的链头的父节点处
}
else
y = fa[tp[y]];//爬到y的链头的父节点处
}
}
好了,可以开始练练手了,练习题是POJ1986:Distance Queries