树链剖分

树链剖分

两个核心思想

  • 将一棵树转化成一个序列
  • 树中路径转化成 log n 段连续区间

相关概念

重儿子:某个节点的子节点所构成的子树中,子树节点数量最多对应的子节点为重儿子,如果有多个相同的最大数量,则任选一个为重儿子,也就是说,每个节点的重儿子只有一个;

轻儿子:除了重儿子,其他儿子节点都为轻儿子

重边:重儿子和其父节点之间的边被称为重边

轻边:重边以外的边为轻边

重链:极大的由重边构成的路径

  • 每个点都在重链中
  • 如果一个点是一个重儿子,那么它就在父节点往下的重链里
  • 如果一个点是一个轻儿子,那么它就在以它往下的重链里

叶子节点没有轻重儿子的概念
请添加图片描述

如何将树变成一个序列

思想:

  • 按dfs序来将树变成一个序列

  • 从根节点开始,每次优先遍历重儿子

    • 好处是保证每次遍历的时候,重链上所有点的编号是连续的

请添加图片描述

重要结论

  • 树中任意一条路径均可拆分成O(log n)个重链
  • 即可拆分成O(log n)个连续区间,因为重链中的点在树上是连续分布的

整个算法流程

  1. dfs 标记每个点的重儿子

    void dfs1(int u,int father,int depth)
    {
        dep[u] = depth,sz[u] = 1,fa[u] = father;
        for(int i : g[u])
        {
            if(i == father) continue;
            dfs1(i,u,depth + 1);
            sz[u] += sz[i];
            if(sz[son[u]] < sz[i]) son[u] = i;
        }
    }
    
  2. 再进行一边dfs,找dfs序和记录重链,实际上记录重链上每个点的顶点就ok

    void dfs2(int u,int t)
    {
        id[u] = ++cnt, nw[cnt] = w[u], top[u] = t;
        if(son[u] == 0) return;
        dfs2(son[u],t);
        for(int i : g[u])
        {
            if(i == fa[u] || i == son[u]) continue;
            dfs2(i,i);
        }
    }
    

对于一条(u,v)路径,怎么去剖分重链呢?

  • 每次选取top[u] 和 top[v] 中深度更大的点,例如:depth[top[u]] > depth[top[v]],剖分top[u] —> u 的一条重链,然后u = fa[top[u]];

  • 当top[u] == top[v]时,说明u,v其中一个点,一定已经是lca(u,v),如果depth[v] < depth[u],说明 v = lca(u,v);额外再剖分一次v -> u
    请添加图片描述

由于dfs2的操作,top[u] -> u 中点的dfs序是连续的,可以看作连续的区间,然后用区间类的数据结构维护即可

void update_path(int u,int v,int d)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u,v);
        update(1,id[top[u]],id[u],d);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u,v);
    update(1,id[v],id[u],d);
}

void update_path(int u,int v,int d)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u,v);
        update(1,id[top[u]],id[u],d);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u,v);
    update(1,id[v],id[u],d);
}

如何去维护一颗子树的信息

由于一棵子树中的dfs序一定是连续的,所以只要知道根节点u和sz[u],就可以为维护u -> u + sz[u] - 1的信息

void update_tree(int u,int d)
{
    update(1,id[u],id[u] + sz[u] - 1,d);
} 

LL query_tree(int u)
{
    return query(1,id[u],id[u] + sz[u] - 1);
}

例题

[P3384 【模板】重链剖分/树链剖分](P3384 [模板]重链剖分/树链剖分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

[Acwing.918. 软件包管理器](918. 软件包管理器 - AcWing题库)

u.com.cn)](https://www.luogu.com.cn/problem/P3384))

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值