• [学习笔记]树链剖分急速入门


    前置知识

    dfs序

    很多树上问题通过dfs序来转换为序列

    线段树

    它是个好东西,或者其他数据结构

    基本概念

    树链剖分通过轻重边剖分将树剖分成多条链,然后利用数据结构来维护这些链

    • 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;

    • 轻儿子:父亲节点中除了重儿子以外的儿子;

    • 重边:父亲结点和重儿子连成的边;

    • 轻边:父亲节点和轻儿子连成的边;

    • 重链:由多条重边连接而成的路径;

    • 轻链:由多条轻边连接而成的路径

    变量声明

    int fa[N];//保存当前节点的父亲
    int dep[N];//保存当前节点深度
    int siz[N];//保存以当前节点为根的子树大小
    int son[N];//保存重儿子
    int top[N];//保存当前节点所在链的顶端节点
    int dfn[N];//保存当前dfs标号在树中所对应的节点
    int id[N];//保存树中每个节点剖分以后的新编号(DFS的执行顺序)
    

    具体实现

    我们通过两遍dfs来预处理上面声明的数组

    dfs1

    求出(fa)(dep)(siz), (son)

    void dfs1(int p, int from)
    {
        siz[p] = 1, dep[p] = dep[from] + 1, fa[p] = from;
        for (int i = f[p]; i; i = e[i].nx)
        {
            if (e[i].to == from) continue;
            dfs1(e[i].to, p);
            siz[p] += siz[e[i].to];
            if (siz[e[i].to] > siz[son[p]]) son[p] = e[i].to;
        }
    }
    

    dfs2

    求出(dfn)(id)(top)

    void dfs2(int p, int tp)
    {
        dfn[++len] = p, id[p] = len, top[p] = tp;
        if (son[p]) dfs2(son[p], tp);//注意这里我们为了保证重链dfs序连续,我们先进入重儿子
        for (int i = f[p]; i; i = e[i].nx)
            if (e[i].to != fa[p] && e[i].to != son[p]) dfs2(e[i].to, e[i].to);
    }
    

    具体操作

    引入问题:
    给你一棵树,支持两种操作:

    1. 修改:将树从x到y结点最短路径上所有节点的值都加上z;
    2. 查询:求树从x到y结点最短路径上所有节点的值之和。

    我们树剖之后,把两点之间的路径分成线段树可以维护的若干段,把这些段加起来便可以得到答案,修改同理

    inline LL query(int u, int v)
    {
        LL ans = 0;
        while (top[u] != top[v]) //如果两点在同一条重链上,就可以直接一次查询完毕了
        {
            if (dep[top[u]] < dep[top[v]]) std::swap(u, v);//链顶深度大的往上跳
            ans = ask(1, id[top[u]], id[u]); //ask是线段树的查询区间和函数
            u = fa[top[u]];
        }
        if (dep[u] < dep[v]) std::swap(u, v);
        return ans + ask(1, id[v], id[u]);
    }
    

    类似的我们可以用树剖求LCA或者更改两点路径上的值

    时间复杂度

    树链剖分的两个显然的性质:

    1. 如果((u, v))是一条轻边,那么(size(v) < size(u)/2)
    2. 从根结点到任意结点的路所经过的轻重链的个数必定都小于(logn)

    可以证明,树链剖分的时间复杂度为(O(nlog^2n))

    题目练习

    1. 模板
      题解略
    2. 稍作变化
      题解略
    3. 有思维难度
      对于线段树,我们多维护一个当前区间左端点的颜色和当前区间右端点的颜色
      所以(pushup)时就变成了这样:
    t[p].lc = t[p << 1].lc, t[p].rc = t[p << 1 | 1].rc;
    t[p].s = t[p << 1].s + t[p << 1 | 1].s;
    if (t[p << 1].rc == t[p << 1 | 1].lc) --t[p].s;
    

    还要注意注意在我们跳链时,假如当前id[top[u]]的颜色与id[fa[top[u]]]的颜色相同,我们要记得给答案减1,因为它们是同种颜色属于同一段而我们把这段切成两段加了两次

  • 相关阅读:
    tf.nn.in_top_k的用法
    随机初始化值randint,rand,tf.random_normal,tf.random_uniform
    归一化输入向量
    softmax函数理解
    梯度下降与反向传播
    深度学习-初始化权重矩阵
    学习--流畅的Python
    基础函数学习
    eclipse
    初学python
  • 原文地址:https://www.cnblogs.com/martixx/p/13883336.html
Copyright © 2020-2023  润新知