首页 > 其他分享 >树链剖分

树链剖分

时间:2024-10-25 11:15:22浏览次数:6  
标签:剖分 color top 树链 重链 节点 red

树链剖分

重链剖分

【问题引入】

问题描述

给定一颗有 $n$ 个节点、带边权的树,现在有对树进行 $m$ 个操作,操作有 $2$ 类:

  • 将节点 $a$ 到节点 $b$ 路径上所有边权的值都改为 $c$;
  • 询问节点 $a$ 到节点 $b$ 路径上的最大边权值。

请你写一个程序依次完成这 $m$ 个操作。

有三个操作

  • 修改 $2$ 号到 $9$ 号节点路径上的边值为 $7$;

  • 修改 $2$ 号到 $7$ 号节点路径上的边值为 $4$ ;

  • 查询 $9$ 号到 $8$ 号节点路径上的最大边权值。

每次修改和查询复杂度为 $O(n)$

$\color{red}思考:既然每个操作都与树上的路径有关,能否把这些路径分段存储,方便修改和查询?$

【树链剖分的概念】

树链剖分是指一种对树进行划分的算法,它先通过轻重边剖分 $(Heavy-Light\ Decomposition)$ 将树分为多条链,保证每个点属于且只属于其中一条链,然后再通过数据结构(树状数组、SBTSPLAY、线段树等)来维护每一条链。

使用这种方法后,一般可以将修改和查询的复杂度降为 $O(log_2\ n)$。

【树链剖分的方法】

定义

将树中的边分为:重边和轻边。

定义 $size(X)$ 为以 $X$ 为根的子树的节点个数。

令 $V$ 为 $U$ 的儿子节点中 $size$ 值最大的节点,那么 $V$ 称为 $U$ 的$\color{red}重儿子$,边 $(U,V)$ 被称为$\color{red}重边$,树中重边之外的边被称为$\color{red}轻边$,全部由重边构成的路径称为$\color{red}重链$。

性质1

对于轻边 $(U,V)$,$size(V)\le size(U)\div 2$。

从根到某一点的路径上,不超过 $log_2 N$ 条轻边,不超过 $log_2 N$ 条重路径。

特例

图中有 $5$ 条重链:

  • $1\to 3\to 6\to 10$
  • $2\to 5\to 9$
  • $4$
  • $7$
  • $8$

性质2

每一条链首深度大于 $1$ 的重链都可以通过其$\color{red}链首的父亲节点$连到$\color{red}另一条重链$上。

核心定义

$size[i]$:以节点 $i$ 为根的子树中节点的个数;

$son[i]$:节点 $i$ 的重儿子;

$dep[i]$:节点 $i$ 的深度,根的深度为 $1$;

$top[i]$:节点 $i$ 所在重链的链首节点;

$fa[i]$:节点 $i$ 的父节点;

$tid[i]$:在 DFS 找重链的过程中为节点 $i$ 重新编的号码,每条重链上的编号节点是连续的。

两次DFS

  • 第一次DFS:找重边,顺便求出所有的 $size[i],dep[i],fa[i],son[i]$;
  • 第二次DFS:将重边连成重链,顺便求出所有的 $top[i],tid[i]$。
第一次DFS

找重边,顺便求出所有的 $size[i],dep[i],fa[i],son[i]$;

第二次DFS

将重边连成重链,顺便求出所有的 $top[i],tid[i]$。

从根节点开始 ,沿重边向下扩展,连成重链;

不能加入当前重链上的节点,以该节点为链首向下拉一条新的重链(如果该节点是叶子节点,则自己构成一条重链);

DFS 过程中,对节点重新编号,因为是沿重边向下扩展,故一条重链上的节点新编号会是连续的。

参考代码

void DFS_2(int u, int sp) {//第二遍dfs求出top[],p[],fp[]的值
    top[u] = sp;
    p[u] = pos++;
    fp[p[u]] = u;
    if (son[u] != -1) DFS_2(son[u], sp);//先处理重边
    for (int i = head[u]; i != -1; i = edge[i].next) {//再处理轻边情况
        int v = edge[i].to;
        if (v != son[u] && v != prt[u])
            DFS_2(v, v);//轻链的顶点就是自己
    }
}

【树链剖分的过程】

重链剖分后

剖分完成后,每条重链相当于一段区间,将所有的重链收尾相接,用适合的数据结构来维护这个整体。

  • $tid[i]$:$\color{red}节点\ i$ 所对应的新编号
  • $rank[i]$:$\color{red}编号\ i$ 所在原树中对应的节点编号

我们可以采用 $\Large\color{red}线段树$等数据结构维护每条重链。

以线段树维护为例

维护节点 $u,v$ 路径上的最大值

【树链剖分的修改操作】

即整体修改点 $U$ 和点 $V$ 的路径上每条边的权值。

点 $U$ 和点 $V$ 的关系分为两种情况:

  • 情况 $1$ :$U$ 和 $V$ 在同一条重链上;
  • 情况 $2$ :$U$ 和 $V$ 不在同一条重链上。

情况 $1$

以修改边为例:将原树中的边($6\to 10$)权值修改为 $6$($\color{red}边的权值存在边所到达顶点中$)。

$10$ 号节点的新编号为 $4$,故只需修改线段树中的 $[4,4]$ 的值即可。

$2$ 号和 $9$ 号节点的新编号分别为 $7$ 和 $9$,需要修改的区间为 $tid[son[2]]\sim tid[9]$,即 $[8,9]$。

情况 $2$

将原树中路径 ($8\to 9$)上所有边的权值都修改为 $8,\ top[9]=2,\ top[8]=8$。

它们不在同一条重链上,需要分段修改,边修改边$\color{red}往一条重链上靠$。

优先将$\color{red}链首$深度大的点往上爬,向另一条重链靠,直到两者爬到同一条重链,转换为情况 $1$ 解决

后面的图不画了。

【树链剖分的查询操作】

和修改操作类似。

设查询 $u\to v\ \max$。

情况 $1$

$top[u] = top[v]$

在线段树上查询 $u\sim v$ 的区间即可。

情况 $2$

$top[u]\ne top[v]$

向上爬树,深度大的优先,深度一样随便爬一个。

重复这个操作,直到 $top[u] = top[v]$,转换成情况 $1$ 处理。

参考代码

int Change(int u, int v) {//查询 u -> v 边的最大值
    int f1 = top[u], f2 = top[v];
    int tmp = 0;
    while (f1 != f2) {
//u 和 v 不在同一条重路径,深度深的点向上爬,直到在同一条重(轻)路径上为止
        if (dep[f1] < dep[f2]) {
            swap(f1, f2);
            swap(u, v);
        }
        tmp = max(tmp, Query(1, p[f1], p[f2]));
        //在重链中求 u 和链首端点 f1 路径上的最大值
        u = prt[f1];
        f1 = top[u];
    }
    if (u == v) return tmp;//为同一点,则退出
    if (dep[u] > dep[v])
        swap(u, v);
    return max(tmp, Query(1, p[son[u]], p[v]));
}
//Query(v, l, r) 查询线段树中 [l,r] 的最大值

实链剖分

没有

长链剖分

没有

标签:剖分,color,top,树链,重链,节点,red
From: https://www.cnblogs.com/zla2012/p/18502087

相关文章

  • 长链剖分 入门
    长链剖分额,其实和树剖差不多,对于每个节点\(u\)维护\(mxd_u\)为子树内节点深度最大值。那么令\(Son(u)\)里取到\(mxd_v\)最大的儿子\(v\)为长儿子,类似重链剖分处理即可。同样令连接不同长链的两个点之间的边为虚边。有如下性质:从根到节点\(u\),所经过虚边个数不超......
  • 【算法】树链剖分
    1.算法简介树链剖分为将树分割成若干条链,维护树上信息的思想。通常将其分为链后能用数据结构维护。树链剖分分为重链剖分,长链剖分,实链剖分。通常重链剖分最常用,本文主要介绍重链剖分。重链剖分可将树划分为一个个长度不超过\(O(\logn)\)的链,并且保证每条链内的\(dfs\)序......
  • 树链剖分笔记
    题单传送门2024.10.12P3038GrassPlantingG:DevC++栈空间开小了;调了三天啊三天线段树区间修改写成区间单点修改了;树剖往上跳写成了dep[u]<dep[v]而不是dep[top[u]]<dep[top[v]]2024.10.15P3128MaxFlowP:奇怪的TLE树剖DFS没把子树大小加到根上,重链剖分写成了后......
  • 树链剖分|树上启发式合并
    树链剖分分为重链剖分和长链剖分以及其他奇怪的剖分。以重剖为主。重链剖分将树上问题重链剖分为序列问题(经常是DFS序)然后用数据结构(经常是线段树)维护。剖分部分定义:重儿子:对于一个点,其儿子中,子树最大的那个;重边:父亲到重儿子的连边;轻儿子:除了重儿子以外的儿子;轻边:父亲......
  • 树链剖分
    考一遍,学一遍,忘一遍这里是重链剖分。两个dfs,第一个找重儿子,第二个找重链顶和dfn(注意要优先对重儿子dfs来保证同一条重链上的dfs序连续)查询和维护时一个一个跳重链顶端,时间复杂度O(nlogn)。常和线段树配套使用。模板#include<bits/stdc++.h>#definelllonglong#defineli......
  • [OI] 树链剖分
    学的时候比较朦胧,现在不朦胧了,所以写一下讲解重儿子:一个节点的子树大小最大的儿子轻儿子:非重儿子重链:节点->重儿子->重儿子..这样的链AbeautifulTree蓝线为重链可以发现,树上的所有节点一定属于且仅属于一个重链首先要知道如何找重链这很简单,可以通过一遍DFS......
  • [学习笔记]树链剖分(简易版) 及其LCA
    树链剖分先讲解一下一些基础定义(都是在树上)重儿子:一个节点中所有儿子中子树大小最大的一个儿子(每个节点最多有一个重儿子)轻儿子:一个节点除重儿子外所有的节点重链:若干个重儿子组成的链链顶:一条链中深度最小的节点以下图为例子(红色连续线段为重链)对于节......
  • 树链剖分
    由于是在树上搞的ds所以考察数据结构本身性质偏多,需大力注重细节。思想考虑将一颗树的链划分成若干个区间进行维护。这种划分方式叫做剖分。约束一颗有根树(有时要求换根但不是真正换根)每个点恰好包含在一条剖出的链中(若被多条链同时包含则需要同时维护多条链,修改多余......
  • 树链剖分
    原理:将一棵树剖分成一条条的链,从而降低时间复杂度首先会一个线段树,书完成剖分后,用来维护每一条的信息。#include<bits/stdc++.h>typedefintintt;#defineintlonglong#definelck<<1#definerck<<1|1constintM=2e6+10;usingnamespacestd;intn,m,ans......
  • 树链剖分
    树链剖分的思想及能解决的问题树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。树链剖分(树剖/链剖)有多种形式,如重链剖分,长链剖分和用于Link/cutTree的剖分(有时被称作「实链剖......