「树链剖分」学习笔记

树链剖分及模板

在树上的两点之间最短路上,或是某一子树上完成一些整体的更新或求和的任务时,需要一些更快的算法。树链剖分的思想是,将树转化为链,也就是将二维转化为一维,然后用我们熟悉的线段树完成。

基本步骤

首先将树进行剖分,一般常用的是轻重边剖分,一遍dfs搞定。然后进行线段树预处理dfs,并处理好top。然后建造线段树,最后在操作的过程中利用top来完成更新或查询。

1.Dfs1——预处理

先给出一些定义:对于每一个节点,统计它的各个子树的节点个数,其中一个节点个数最多的子树称之为重儿子,其余为轻儿子。连接重儿子与自己的边叫重边,其余叫轻边。重边所连成的链称之为重链。

因此每个点都在且只在一条重链上。

在这一次DFS中,我们的主要任务就是统计出size

所以我们可以在Dfs的过程中统计出重边(重儿子),同时预处理出每个节点的深度,父亲,与子树中的节点个数。

int dfs1(int x, int father, int d){
    dep[x] = d;
    fa[x] = father;
    size[x] = 1;
    int sz = G[x].size(), to,_mx=-1;
    for(int i = 0; i < sz; ++i){
        to = G[x][i];
        if(to == father) continue;
        size[x] += dfs1(to,x,d+1);
        if(size[to] > _mx){
            _mx = size[to];
            son[x] = to;
        }
    }
    return size[x];
}

2.Dfs2——剖分

我们现在要将这个数上的节点映射到线段树上,因此每个节点一定要对应一个线段树上叶子的编号,这样才能建立线段树。

由于我们已经标记好了size,我们希望一条重链上的节点对应的线段树的编号是连续的,这样就可以方便地更新了。如何来完成?只需要先DFS重儿子下去,这样时间戳自然就排好了。

另外,我们要计算top值。所谓top值也就是一个节点所在重链中深度最小的那一个节点。对于重儿子,top选择继承。对于轻儿子,top是它自己。由于每个点都在且只在一条重链上,所以每个点的top是唯一确定的。这个top有什么用?到第四步的时候再说

 

void dfs2(int x, int topf){
    idx[x] = ++cnt;
    a[cnt] = w[x];
    top[x] = topf;
    if(son[x]){
        dfs2(son[x], topf);
        int sz = G[x].size();
        for(int i = 0; i < sz; ++i){
            if(!idx[G[x][i]]){
                dfs2(G[x][i], G[x][i]);    
            }
        }
    }
}

 

3.线段树的建树

不再赘述,请看这里

4.树上操作

树链剖分的重中之重。

(1)任意两点之间最短路径上的操作

树上任意两点之间的最短路是唯一确定,并且必定经过LCA。

这回我们可以利用到我们的top值了。选择两点中top较低的那一个点跳到top的上方,并且处理掉这一条被跳过的重链。然后在选一个再跳……直到这两个点在同一条重链上为止。最后再累积一下这一段即可。这样我们就通过了一次次的对链的操作完成了对树的操作。这就是我们为什么要求top,并且要求一条重链上的编号需要连续——因为我们每一次跳过的链都需要用线段树来更新或求解,这样就方便而快捷。

为什么是选择top值较低的来跳呢?回到我们的目的来看,我们的目的是要让这两个点最终跳到同一条链上,来完成操作。而在这之前,他们都低于他们的LCA。事实上,这就是在求解LCA。跳到同一条重链时上方的那个点就是LCA!所以树链剖分也可以轻松求解LCA(还不用打线段树哦~)

inline void TreeAdd(int x, int y, int z){
    while(top[x] != top[y]){
        if(dep[top[x]] < dep[top[y]]) Swap(x,y);
        Update(1,n,1,idx[top[x]],idx[x],z);
        x = fa[top[x]];
    }
    if(idx[x] > idx[y]) Swap(x,y);
    Update(1,n,1,idx[x],idx[y],z);
}

(2)子树上的操作

对于节点i的子树,在线段树里对应的编号一定是i ~ i + size[i] - 1,因为这一个子树的编号是一遍dfs完成的,所以一定是连续的。因此子树操作自然就解决了。

 

posted @ 2018-06-30 14:14  DennyQi  阅读(162)  评论(0编辑  收藏  举报