• 「树链剖分」学习笔记


    树链剖分及模板

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

    基本步骤

    首先将树进行剖分,一般常用的是轻重边剖分,一遍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完成的,所以一定是连续的。因此子树操作自然就解决了。

  • 相关阅读:
    前进的道路,不要往后看——记得24生日
    【剑指offer】:Q44:直扑克
    手游client思考框架
    Craig可能是个冲浪爱好者
    VMware vSphere服务器虚拟化实验六 vCenter Server 添加储存
    OSX: 使用命令行对FileVault2分区恢复
    让工程师爱上CMMI,实现管理于无形 --- 中标软件CMMI L5之路 (1/2)
    让工程师爱上CMM,实现管理于无形 --- 中标软件CMMI L5之路 (2/2)
    HTML+CSS实例——漂亮的查询部件(一)
    jbpm部署流程定义到MySql报乱码解决方案
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9247039.html
Copyright © 2020-2023  润新知