树链剖分

基本概念

  • 树链剖分:就是把树中的节点划分成若干条不相交的链,然后通过对这些链进行处理来解决树相关的问题。其核心思想是将树中的节点按照一定规则划分成重链和轻链,使得树的结构在某种程度上变得更易于处理,能将一些与树相关的操作的时间复杂度优化到可接受的范围。
  • 重儿子与轻儿子:对于树中的每个节点,其子树大小最大的儿子节点称为重儿子,如果有多个子树大小相同的儿子,可任选一个作为重儿子。其余儿子节点就是轻儿子。
  • 重边与轻边:连接节点和它的重儿子的边称为重边,连接节点和它的轻儿子的边称为轻边。
  • 重链与轻链:由重边连接而成的路径称为重链,轻边连接而成的路径称为轻链。

实现步骤

  • 预处理
  1. 深度优先搜索(DFS)计算子树大小等信息:从根节点开始进行深度优先搜索,在搜索过程中,计算每个节点的子树大小、深度、父节点等信息。
  2. 再次 DFS 确定重链和轻链:通过第二次深度优先搜索,根据子树大小等信息确定每个节点的重儿子和轻儿子,进而确定重边和轻边,划分出重链和轻链。同时,可以给每个节点分配一个在线性结构中的编号,一般按照深度优先遍历的顺序来编号,这样可以将树结构映射到线性结构上,方便后续操作。
  • 查询与修改操作
  1. 将树链上的操作转化为区间操作:当需要对树中某条路径上的节点进行操作(如求和、求最大值、修改值等)时,利用树链剖分将路径分解为若干条重链和轻边。由于重链已经映射到线性结构上,所以对重链上的操作可以转化为对线性区间的操作,这可以借助线段树、树状数组等数据结构来高效实现。对于轻边,由于其数量相对较少,直接进行暴力操作或者结合其他数据结构进行处理,不会影响整体的时间复杂度。

时间复杂度

  • 预处理时间复杂度:两次深度优先搜索的时间复杂度为,其中是树中节点的数量。因为每个节点和每条边都只被访问常数次。
  • 查询与修改操作时间复杂度:在进行查询和修改操作时,由于树链剖分将树中的路径分解为了重链和轻边,而重链上的操作可以高效地通过线段树等数据结构来处理,轻边的数量相对较少,所以每次查询或修改操作的时间复杂度为。

应用场景

  • 树上路径问题:如求树上两点之间路径上的节点权值之和、最大值、最小值等。
  • 树上节点修改:对树中某个节点或某条路径上的节点权值进行修改,然后查询相关信息。
  • 动态树问题:在一些动态树的问题中,树的结构可能会发生变化,如添加或删除边、节点等操作,树链剖分可以与其他数据结构结合,高效地处理这些动态操作。

板子

#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;
}
posted @ 2025-02-24 19:12  流氓兔LMT  阅读(59)  评论(0)    收藏  举报