当前位置: 首页 > news >正文

树链剖分(模板 + 思路)

树链剖分(模板 + 思路)

  • 概念
  • 思路解释
    • 第一个dfs (递归版)
    • 第一个dfs(迭代版)
    • 第二个dfs (递归版)
    • 第二个dfs(迭代版)
  • 最终代码

模板题目链接
左神视频讲解

概念

树链剖分是一种将树结构分解为线性链的算法,用于高效处理树上路径查询(如路径和、最值)和子树修改操作。其核心思想是将树边分为重边和轻边,形成连续的重链,从而利用线段树等数据结构加速操作。

重子节点:节点uuu的子节点中子树大小最大的子节点
轻子节点:uuu的其他子节点
重边:连接uuu与其重子节点的边
轻边:连接uuu与其轻子节点的边
重链:由重边连接形成的极大路径

思路解释

int fa[N],son[N],dep[N],siz[N],top[N],seg[N],dfn[N],cntd;
fa[i]:表示 i 节点的父亲节点编号
son[i]:表示 i 节点的儿子节点编号
dep[i]:表示 i 节点的深度
siz[i]:表示 i 节点为根的子树大小
top[i]:表示 i 节点所在链的头节点
seg[i]:表示第 i 个编号的节点是什么
dfn[i]:表示第 i 个节点的编号是什么
cntd:用来给链中的节点编号。

因为dfs会爆栈,所以要改成迭代版本。

第一个dfs (递归版)

void dfs_1(int u,int v) {fa[u] = v;dep[u] = dep[v] + 1;siz[u] = 1;int maxh = 0;for (int i = h[u]; i != -1; i = ne[i]) {int t = e[i];if (t != v) {dfs_1(t,u);siz[u] += siz[t];maxh = max(maxh,siz[t]);if (siz[son[u]] < maxh) {son[u] = t;}}}
}

第一个dfs(迭代版)

// 递归DFS实现(使用栈扩展)
void dfs_1(int root) {stack<int> st;st.push(root);fa[root] = 0;dep[root] = 1;vector<int> order;while (!st.empty()) {int u = st.top();st.pop();order.push_back(u);for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u]) continue;fa[v] = u;dep[v] = dep[u] + 1;st.push(v);}}// 逆序处理计算子树大小和重儿子reverse(order.begin(), order.end());for (int u : order) {siz[u] = 1;son[u] = 0;for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u]) continue;siz[u] += siz[v];if (siz[v] > siz[son[u]]) {son[u] = v;}}}
}

第二个dfs (递归版)

void dfs_2(int u,int v) {top[u] = v;dfn[u] = ++cntd;seg[cntd] = u;if (son[u] == 0) {//!!!! 表示已无重儿子,向上返回。return;}dfs_2(son[u],v);for (int i = h[u]; i != -1; i = ne[i]) {int t = e[i];if (t != v && t != son[u]) {dfs_2(t,t);}}
}

第二个dfs(迭代版)

void dfs_2(int root, int top_val) {stack<pair<int, int>> st;st.push({root, top_val});while (!st.empty()) {int u = st.top().first;int t = st.top().second;st.pop();top[u] = t;dfn[u] = ++cntd;seg[cntd] = u;// 先处理轻儿子,再处理重儿子(因为栈是LIFO)for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u] || v == son[u]) continue;st.push({v, v});}// 最后处理重儿子if (son[u] != 0) {st.push({son[u], t});}}
}

最终代码

需要掌握链式前向星建图法 + 线段树修改(配合使用懒更新)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;int a[N], e[2 * N], ne[2 * N], h[N], idx, n, MOD;
int fa[N], son[N], dep[N], siz[N], top[N], seg[N], dfn[N], cntd;
ll sum[4 * N], addtag[4 * N];void addline(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}// 线段树部分
void up(int i) {sum[i] = (sum[i << 1] + sum[i << 1 | 1]) % MOD;
}void lazy(int i, ll val, int n) {sum[i] = (sum[i] + val * n) % MOD;addtag[i] = (addtag[i] + val) % MOD;
}void down(int i, int ln, int rn) {if (addtag[i] != 0) {lazy(i << 1, addtag[i], ln);lazy(i << 1 | 1, addtag[i], rn);addtag[i] = 0;}
}void build(int l, int r, int i) {if (l == r) {sum[i] = a[seg[l]] % MOD;return;}int mid = (l + r) >> 1;build(l, mid, i << 1);build(mid + 1, r, i << 1 | 1);up(i);
}void add(int jobl, int jobr, int jobv, int l, int r, int i) {if (jobl <= l && r <= jobr) {lazy(i, jobv, r - l + 1);return;}int mid = (l + r) >> 1;down(i, mid - l + 1, r - mid);if (jobl <= mid)add(jobl, jobr, jobv, l, mid, i << 1);if (jobr > mid)add(jobl, jobr, jobv, mid + 1, r, i << 1 | 1);up(i);
}ll query(int jobl, int jobr, int l, int r, int i) {if (jobl <= l && r <= jobr) {return sum[i] % MOD;}int mid = (l + r) >> 1;down(i, mid - l + 1, r - mid);ll ans = 0;if (jobl <= mid)ans = (ans + query(jobl, jobr, l, mid, i << 1)) % MOD;if (jobr > mid)ans = (ans + query(jobl, jobr, mid + 1, r, i << 1 | 1)) % MOD;return ans;
}// 递归DFS实现(使用栈扩展)
void dfs_1(int root) {stack<int> st;st.push(root);fa[root] = 0;dep[root] = 1;vector<int> order;while (!st.empty()) {int u = st.top();st.pop();order.push_back(u);for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u]) continue;fa[v] = u;dep[v] = dep[u] + 1;st.push(v);}}// 逆序处理计算子树大小和重儿子reverse(order.begin(), order.end());for (int u : order) {siz[u] = 1;son[u] = 0;for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u]) continue;siz[u] += siz[v];if (siz[v] > siz[son[u]]) {son[u] = v;}}}
}void dfs_2(int root, int top_val) {stack<pair<int, int>> st;st.push({root, top_val});while (!st.empty()) {int u = st.top().first;int t = st.top().second;st.pop();top[u] = t;dfn[u] = ++cntd;seg[cntd] = u;// 先处理轻儿子,再处理重儿子(因为栈是LIFO)for (int i = h[u]; i != -1; i = ne[i]) {int v = e[i];if (v == fa[u] || v == son[u]) continue;st.push({v, v});}// 最后处理重儿子if (son[u] != 0) {st.push({son[u], t});}}
}// 树链剖分操作
void add_path(int x, int y, int val) {val %= MOD;while (top[x] != top[y]) {if (dep[top[x]] < dep[top[y]]) swap(x, y);add(dfn[top[x]], dfn[x], val, 1, n, 1);x = fa[top[x]];}if (dep[x] > dep[y]) swap(x, y);add(dfn[x], dfn[y], val, 1, n, 1);
}void add_tree(int u, int val) {val %= MOD;add(dfn[u], dfn[u] + siz[u] - 1, val, 1, n, 1);
}ll path_sum(int x, int y) {ll ans = 0;while (top[x] != top[y]) {if (dep[top[x]] < dep[top[y]]) swap(x, y);ans = (ans + query(dfn[top[x]], dfn[x], 1, n, 1)) % MOD;x = fa[top[x]];}if (dep[x] > dep[y]) swap(x, y);ans = (ans + query(dfn[x], dfn[y], 1, n, 1)) % MOD;return ans;
}ll tree_sum(int u) {return query(dfn[u], dfn[u] + siz[u] - 1, 1, n, 1) % MOD;
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);memset(h, -1, sizeof(h));int m, r;cin >> n >> m >> r >> MOD;for (int i = 1; i <= n; i++) {cin >> a[i];}for (int i = 1; i < n; i++) {int u, v;cin >> u >> v;addline(u, v);addline(v, u);}dfs_1(r);dfs_2(r, r);build(1, n, 1);while (m--) {int op, x, y, z;cin >> op;if (op == 1) {cin >> x >> y >> z;add_path(x, y, z);} else if (op == 2) {cin >> x >> y;cout << path_sum(x, y) << '\n';} else if (op == 3) {cin >> x >> z;add_tree(x, z);} else {cin >> x;cout << tree_sum(x) << '\n';}}return 0;
}

预处理各种信息(上述7个数组):O(n)O(n)O(n)
路径查询/修改:O(log⁡2n)O(\log^2 n)O(log2n)

http://www.dtcms.com/a/394674.html

相关文章:

  • 医疗数据互操作性与联邦学习的python编程方向研究(上)
  • Windows最新摆烂更新,让用户没法看视频了
  • 可配置化App启动弹窗系统:实现后台动态管理与热更新引导-蜻蜓Q系统laravel+vue3-优雅草卓伊凡
  • Permute 媒体文件格式转换【音视频图像文件转换】(Mac电脑)
  • Netty:实现RPC服务(实战)
  • 408复习笔记—MIPS指令系统
  • 阿里万相2.1:蓝耘MaaS平台部署 vs 官网在线使用:万字实测对比与深度技术解析
  • 11月长春EI会议:ISRAI 2025 诚邀学者参与投稿
  • 【AI时代速通QT】第七节:Visual Studio+Qt 开发指南
  • 医疗问诊陪诊小程序:全方位守护就医体验的功能宝库
  • iOS 开发环境搭建完整指南 Xcode 安装配置、iOS 开发工具选择、ipa 打包与 App Store 上架实战经验
  • 【Node.js】Express 和 Koa 中间件的区别
  • 学习路之PHP--TP8+swoole
  • 【从零开始的大模型原理与实践教程】--第五章:动手搭建大模型LLaMA2
  • Vue.js 从入门到实践1:环境搭建、数据绑定与条件渲染
  • “潮涌之江,文兴浙里”文化推动高质量发展主题活动在西湖区调研
  • 【MongoDB】mongoDB数据迁移
  • 《C++多态入门:轻松理解虚函数与多态编程》
  • 虚拟化范式跃迁中的生命周期隐喻与命令哲学——解构Docker容器从抽象实体到可控资源的数字化生存法则
  • OpenLayers地图交互 -- 章节八:平移交互详解
  • AES+RSA 实现混合加密
  • 命名实体识别技术NER
  • 网络验证 一键加密 一键接入验证 加壳加密数盾加盾
  • JDBC组件
  • StandardScaler,MinMaxScaler等四个内置归一化函数学习
  • pandawiki 无法获取模型列表
  • openEuler2403安装宝塔面板
  • Altium Designer(AD) PCB铺铜
  • 解决Django长时间操作中的MySQL连接超时问题
  • 样本量估计原理与python代码实现