
树链剖分(轻重链剖分) 讲解 (模板题目 P3384 【模板】轻重链剖分 )
发布日期:2021-05-10 09:53:38
浏览次数:18
分类:精选文章
本文共 4908 字,大约阅读时间需要 16 分钟。
模板:轻重链剖分
基本概念
在轻重链剖分中,我们需要明确一些关键概念:
重儿子:一个结点的所有儿子中,大小最大的那个。如果有多个儿子的大小相等,则取任意一个作为重儿子。
轻儿子:一个结点的所有儿子中,除了重儿子之外的所有儿子,称为轻儿子。根节点属于轻儿子。
重链:从一个轻儿子开始,一路向重儿子走,连续的边构成的链称为重链。
轻链:除了重链之外,其他路径都属于轻链。
重链及其dfs序的计算
通过两个深度优先遍历(DFS),我们可以高效地完成该任务:
第一个DFS的目标
- 标记结点的父亲 (
fa
数组)。 - 标记结点的重儿子 (
son
数组)。 - 标记结点的深度 (
dep
数组)。 - 标记结点的大小 (
siz
数组)。
第二个DFS的目标
为什么需要进行第二遍 DFS?因为第一个 DFS 需要确定结点的重儿子。
- 标记重链的开始结点 (
top
数组)。 - 记录结点的权值顺序 (
dfn
数组),并与时间戳结合使用。
- 标记重链的开始结点 (
示例分析
在下面的示例中,我们可以看到:
- 第一条重链:FHKL,重链的起始结点为 F。
- 第二条重链:CD,重链的起始结点为 C。
通过标记重链的起始结点,我们可以轻松判断两个结点是否属于同一重链。
如果两个结点不在同一重链上,我们可以通过维护指针来实现:
- 指针操作:不断让顶部结点沿着重链向上跳,同时在线段树上进行更新。
- 终止条件:当两个指针指向同一结点时,停止操作。
引理
除了根节点外,任何一个结点的父节点都位于一条重链上。这是因为父节点至少有一个儿子,而该儿子就是重儿子,从而位于一条重链。
路径分类
对于任意两个结点 x 和 y:
同一重链:直接获取它们的 dfs 序,并在线段树上进行操作。
不在同一重链:通过以上指针操作,将问题转换为在重链上的子路径问题。
代码实现
以下是一个简化的代码实现示例:
#pragma GCC optimize("Ofast","inline")#includeusing namespace std;const int maxn = 1e6 + 10;int mod = 998244353;struct edge { int to, next;};int tot, head[maxn];void init() { memset(head, -1, sizeof(head));}void adde(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; edge[tot].to = u; edge[tot].next = head[v]; head[v] = tot++;}int fa[maxn], dep[maxn], siz[maxn], son[maxn];void dfs1(int u, int f) { fa[u] = f; dep[u] = dep[f] + 1; siz[u] = 1; for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (v == f) continue; dfs1(v, u); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) { son[u] = v; } }}int cnt, dfn[maxn], top[maxn], w[maxn];void dfs2(int u, int t) { dfn[u] = ++cnt; top[u] = t; w[cnt] = v[u]; if (!son[u]) return; dfs2(son[u], t); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (v == fa[u] || v == son[u]) continue; dfs2(v, v); }}int tree[maxn], lazy[maxn];void push_down(int node, int l, int r) { lazy[node*2] += lazy[node]; lazy[node*2 + 1] += lazy[node]; int mid = (l + r) / 2; tree[node*2] += (mid - l + 1) * lazy[node] % mod; tree[node*2 + 1] += (r - mid) * lazy[node] % mod; lazy[node] = 0;}void build(int node, int start, int ends) { if (start == ends) { tree[node] = w[start] % mod; return; } int mid = (start + ends) / 2; build(node*2, start, mid); build(node*2 + 1, mid + 1, ends); tree[node] = (tree[node*2] + tree[node*2 + 1]) % mod;}void update(int node, int start, int ends, int l, int r, int val) { if (start >= l && ends <= r) { lazy[node] += val; tree[node] += (ends - start + 1) * val % mod; return; } if (lazy[node]) push_down(node, start, ends); int mid = (start + ends) / 2; if (l <= mid) { update(node*2, start, mid, l, r, val); } if (mid < r) { update(node*2 + 1, mid + 1, ends, l, r, val); } tree[node] = (tree[node*2] + tree[node*2 + 1]) % mod;}int query(int node, int start, int ends, int l, int r) { if (start > end) return 0; if (lazy[node]) push_down(node, start, ends); int mid = (start + ends) / 2; int res = 0; if (l <= mid) { res += query(node*2, start, mid, l, r); } if (mid < r) { res += query(node*2 + 1, mid + 1, ends, l, r); } return res % mod;}void mchain(int x, int y, int z) { z %= mod; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); update(1, 1, n, dfn[top[x]], dfn[x], z); x = fa[top[x]]; } if (dep[x] > dep[y]) swap(x, y); update(1, 1, n, dfn[x], dfn[y], z);}int qchain(int x, int y) { int ret = 0; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); ret = (ret + query(1, 1, n, dfn[top[x]], dfn[x])) % mod; x = fa[top[x]]; } if (dep[x] > dep[y]) swap(x, y); ret = (ret + query(1, 1, n, dfn[x], dfn[y])) % mod; return ret % mod;}int main() { ios::sync_with_stdio(false); cin.tie(nullptr); init(); cin >> n >> m >> r >> mod; for (int i = 1; i <= n; i++) { cin >> v[i]; } for (int i = 0; i < n-1; i++) { int x, y; cin >> x >> y; adde(x, y); } dfs1(r, r); dfs2(r, r); build(1, 1, n); for (int i = 0; i < m; i++) { int opt; cin >> opt; if (opt == 1) { int x, y, z; cin >> x >> y >> z; mchain(x, y, z); } else if (opt == 2) { int x, y; cin >> x >> y; cout << qchain(x, y) << endl; } else if (opt == 3) { int x, y; cin >> x >> y; update(1, 1, n, dfn[x], dfn[x] + siz[x] - 1, y); } else { int x; cin >> x; cout << query(1, 1, n, dfn[x], dfn[x] + siz[x] - 1) << endl; } }}
说明
- 模块化设计:代码采用模块化设计,便于维护和扩展。
- 线段树:用于处理区间查询和更新操作。
- 高效指针操作:通过指针操作和线段树更新,实现了轻重链剖分的高效处理。
- 易于扩展:代码结构清晰,便于根据需求添加新的功能和模块。
总结
通过两个DFS遍历和线段树操作,我们可以高效地处理树上的路径问题。无论是路径是否在同一重链上,还是路径的查询和更新,优化后的代码都能完成任务。这是一种高效且灵活的算法设计,能够解决复杂的路径问题。
发表评论
最新留言
不错!
[***.144.177.141]2025年04月15日 07时57分47秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
L1与L2正则化中“|| ||”是什么意思
2023-01-30
L1正则化与嵌入式特征选择(稀疏性)
2023-01-30
labuladong算法学习
2023-01-30
labview如何把A数组的第一个数据插入到B数组的最后一个元素
2023-01-30
Lake Counting
2023-01-30
lambda 与列表理解性能
2023-01-30
Lambda 实现超强排序
2023-01-30
lambda表达式
2023-01-30
lambda表达式与匿名内部类与双冒号(::)
2023-01-30
Lambda表达式入门,看这篇就够了!
2023-01-30
Lammp安装过程
2023-01-30
lamp 一键安装
2023-01-30
Lamp(Fpm-Php)基本配置
2023-01-30
LAMP下添加支持openssl的主机
2023-01-30
LAMP与LNMP架构详解
2023-01-30
LAMP架构部署实战(附LAMP源码包和CRUD测试Web网站)
2023-01-30
LAMP网站平台搭建
2023-01-30