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

前置知识

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,因为它们是同种颜色属于同一段而我们把这段切成两段加了两次

原文地址:https://www.cnblogs.com/martixx/p/13883336.html