树链剖分
基本概念
- 树链剖分:就是把树中的节点划分成若干条不相交的链,然后通过对这些链进行处理来解决树相关的问题。其核心思想是将树中的节点按照一定规则划分成重链和轻链,使得树的结构在某种程度上变得更易于处理,能将一些与树相关的操作的时间复杂度优化到可接受的范围。
- 重儿子与轻儿子:对于树中的每个节点,其子树大小最大的儿子节点称为重儿子,如果有多个子树大小相同的儿子,可任选一个作为重儿子。其余儿子节点就是轻儿子。
- 重边与轻边:连接节点和它的重儿子的边称为重边,连接节点和它的轻儿子的边称为轻边。
- 重链与轻链:由重边连接而成的路径称为重链,轻边连接而成的路径称为轻链。
实现步骤
- 预处理
- 深度优先搜索(DFS)计算子树大小等信息:从根节点开始进行深度优先搜索,在搜索过程中,计算每个节点的子树大小、深度、父节点等信息。
- 再次 DFS 确定重链和轻链:通过第二次深度优先搜索,根据子树大小等信息确定每个节点的重儿子和轻儿子,进而确定重边和轻边,划分出重链和轻链。同时,可以给每个节点分配一个在线性结构中的编号,一般按照深度优先遍历的顺序来编号,这样可以将树结构映射到线性结构上,方便后续操作。
- 查询与修改操作
- 将树链上的操作转化为区间操作:当需要对树中某条路径上的节点进行操作(如求和、求最大值、修改值等)时,利用树链剖分将路径分解为若干条重链和轻边。由于重链已经映射到线性结构上,所以对重链上的操作可以转化为对线性区间的操作,这可以借助线段树、树状数组等数据结构来高效实现。对于轻边,由于其数量相对较少,直接进行暴力操作或者结合其他数据结构进行处理,不会影响整体的时间复杂度。
时间复杂度
- 预处理时间复杂度:两次深度优先搜索的时间复杂度为,其中是树中节点的数量。因为每个节点和每条边都只被访问常数次。
- 查询与修改操作时间复杂度:在进行查询和修改操作时,由于树链剖分将树中的路径分解为了重链和轻边,而重链上的操作可以高效地通过线段树等数据结构来处理,轻边的数量相对较少,所以每次查询或修改操作的时间复杂度为。
应用场景
- 树上路径问题:如求树上两点之间路径上的节点权值之和、最大值、最小值等。
- 树上节点修改:对树中某个节点或某条路径上的节点权值进行修改,然后查询相关信息。
- 动态树问题:在一些动态树的问题中,树的结构可能会发生变化,如添加或删除边、节点等操作,树链剖分可以与其他数据结构结合,高效地处理这些动态操作。
板子
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m, root, mod;
// 树的边
vector<int> g[N];
// 节点权值
int w[N];
int head[N], to[N], nxt[N], w[N];
int tot;
void add(int a, int b, int c)
{
nxt[++tot] = head[a], head[a] = tot, to[tot] = b, w[tot] = c;
}
// 树链剖分相关数组
// 每个节点的父节点
int fa[N];
// 每个节点的深度
int dep[N];
// 每个节点的子树大小
int siz[N];
// 每个节点的重儿子
int son[N];
// 节点在线性结构中的编号
int seg[N];
// 每个节点的链头
int top[N];
// 在线性结构中的位置所对应的树中结点编号
int rev[N];
// 点权
int num[N];
// 线段树相关数组
int tr[N << 2];
// 第一次dfs计算fa[],dep[],siz[],son[];
void dfs1(int u, int f, int depth)
{
fa[u] = f;
dep[u] = depth;
siz[u] = 1;
for (int i = head[u]; i; i = nxt[u])
{
int v = to[i];
if (v == f)
continue;
dfs1(v, u, depth + 1);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
}
// 第二次dfs确定重链和轻链,给节点编号
void dfs2(int u)
{
if(son[u])
{
seg[son[u]]=++seg[0];
rev[seg[0]]=son[u];
top[son[u]]=top[u];
dfs2(son[u]);
}
for(int i=head[i];i;i=nxt[i])
{
if(!top[u])
{
int v=to[i];
seg[v]=++seg[0];
rev[seg[0]]=son[u];
top[v]=v;
dfs2(v);
}
}
}
// 线段树的build操作
void build(int u, int l, int r)
{
if (l == r)
{
tr[u] = num[rev[l]];
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
// 线段树的query操作
int query(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return tr[u];
int mid = l + r >> 1;
int res = 0;
if (L <= mid)
res += query(u << 1, l, mid, L, R);
if (R > mid)
res += query(u << 1 | 1, mid + 1, r, L, R);
return res;
}
// 树链剖分的query操作,求u到v路径上的节点权值之和
int tree_query(int u, int v)
{
int res = 0;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res += query(1, 1, n, seg[top[u]], seg[u]);
u = fa[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
res += query(1, 1, n, seg[u], seg[v]);
return res;
}
本文来自博客园,作者:流氓兔LMT,转载请注明原文链接:https://www.cnblogs.com/-include-lmt/p/18734682

浙公网安备 33010602011771号