线段树分治:原理、实现与应用 一、什么是线段树分治线段树分治是一种基于时间维度的离线算法思想它通过将操作或事件按照时间区间分配到线段树的节点上然后通过遍历线段树来模拟时间的推进与回溯从而高效处理具有时间区间属性的问题。其核心思想是将每个具有生效时间区间 [l, r] 的操作“挂载”到线段树对应的节点上然后对线段树进行深度优先遍历DFS。在进入一个节点时执行该节点上挂载的所有操作在离开该节点时撤销这些操作的影响即“回溯”。这样当遍历到叶子节点代表某个具体时刻时当前维护的状态就是该时刻下所有生效操作叠加后的结果。二、算法框架与模板线段树分治的通用实现框架如下以 C 为例struct Operation { int l, r; // 操作生效的时间区间 [l, r) // ... 其他操作相关数据 }; vectorOperation opTree[N * 4]; // 线段树节点存储操作列表 int timeMax; // 总时间范围 // 将操作插入到线段树对应节点 void addOperation(int u, int l, int r, int ql, int qr, const Operation op) { if (ql l r qr) { opTree[u].push_back(op); return; } int mid (l r) 1; if (ql mid) addOperation(u * 2, l, mid, ql, qr, op); if (qr mid) addOperation(u * 2 1, mid, r, ql, qr, op); } // DFS 遍历线段树处理操作 void dfs(int u, int l, int r) { // 进入节点执行该节点所有操作 for (auto op : opTree[u]) { apply(op); // 应用操作更新当前状态 } if (r - l 1) { // 到达叶子节点时刻 l处理查询 answerQuery(l); } else { int mid (l r) 1; dfs(u * 2, l, mid); dfs(u * 2 1, mid, r); } // 离开节点撤销操作回溯 for (auto op : opTree[u]) { undo(op); // 撤销操作恢复状态 } }三、典型应用场景1. 动态图连通性维护一个无向图边在时间区间 [l, r) 内存在询问某个时刻图的连通分量数量或两点是否连通。使用可撤销并查集配合线段树分治复杂度为 O((QE) log Q α(N))。2. 带时间限制的背包问题物品在特定时间区间内可用询问某个时刻的背包最优解。将物品按时间区间插入线段树DFS 时维护当前可用的物品集合进行 DP回溯时撤销 DP 状态。3. 离线处理区间赋值问题有一系列区间赋值操作每个操作在特定时间区间内生效询问某个时刻某个位置的值。将操作挂载到线段树DFS 时使用数据结构如线段树或平衡树维护当前赋值情况。四、关键技巧与优化可撤销数据结构并查集、线性基、凸包等数据结构需要支持快速撤销最后一步操作。时间离散化将时间点离散化为整数方便线段树建树。操作合并同一节点上的多个操作可以批量处理减少状态切换开销。内存优化使用 vector 存储操作DFS 结束后及时清空。五、例题与代码实现例题动态图连通性LOJ #121给定一个无向图边在时间区间内存在询问某个时刻两个点是否连通。#include bits/stdc.h using namespace std; struct DSU { vectorint fa, sz; vectorpairint, int history; DSU(int n) : fa(n), sz(n, 1) { iota(fa.begin(), fa.end(), 0); } int find(int x) { while (fa[x] ! x) x fa[x]; return x; } bool merge(int x, int y) { x find(x), y find(y); if (x y) return false; if (sz[x] sz[y]) swap(x, y); history.emplace_back(y, fa[y]); fa[y] x; sz[x] sz[y]; return true; } void undo() { auto [y, oldFa] history.back(); history.pop_back(); sz[fa[y]] - sz[y]; fa[y] oldFa; } }; struct Edge { int u, v, l, r; }; vectorEdge edges; vectorpairint, int queries; mappairint, int, int mp; // 记录边上次出现的时间 vectorvectorint tree; vectorint ans; void add(int u, int l, int r, int ql, int qr, int eid) { if (ql r || qr l) return; if (ql l r qr) { tree[u].push_back(eid); return; } int mid (l r) 1; add(u * 2, l, mid, ql, qr, eid); add(u * 2 1, mid, r, ql, qr, eid); } void dfs(int u, int l, int r, DSU dsu) { int cnt 0; for (int eid : tree[u]) { auto e edges[eid]; if (dsu.merge(e.u, e.v)) cnt; } if (r - l 1) { if (l queries.size()) { auto [x, y] queries[l]; ans[l] (dsu.find(x) dsu.find(y)); } } else { int mid (l r) 1; dfs(u * 2, l, mid, dsu); dfs(u * 2 1, mid, r, dsu); } while (cnt--) dsu.undo(); } int main() { int n, m, T; cin n m T; for (int i 0; i m; i) { int u, v, l, r; cin u v l r; u--; v--; if (l r) continue; edges.push_back({u, v, l, r}); } int q; cin q; queries.resize(q); ans.resize(q); for (int i 0; i q; i) { int x, y; cin x y; x--; y--; queries[i] {x, y}; } // 建树 int sz 1; while (sz q) sz 1; tree.resize(sz * 2); // 添加边到线段树 for (int i 0; i edges.size(); i) { auto e edges[i]; add(1, 0, sz, e.l, e.r, i); } DSU dsu(n); dfs(1, 0, sz, dsu); for (int i 0; i q; i) { cout (ans[i] ? Yes : No) \n; } return 0; }六、总结线段树分治是一种强大的离线算法范式它将时间维度转化为空间维度通过 DFS 遍历与状态回溯优雅地解决了大量带时间区间约束的问题。掌握这一思想能够显著提升解决复杂离线问题的能力。核心要点总结将操作按时间区间插入线段树节点DFS 遍历线段树进入节点时应用操作离开时撤销叶子节点对应具体时刻此时的状态即为答案需要配合可撤销数据结构实现高效回溯。