一些使用技巧
默认读者已经熟练掌握:线段树各种标记、树状数组、主席树、FHQ Treap、树链剖分、点分治、普通莫队、CDQ 分治、LCT 的基本操作。本讲不讲这些的实现,只讲它们背后的原理,以及在这些原理之上再往上走一层的东西。
组织方式不按结构名,按六个原理。每个原理先讲"为什么成立",再讲"能推出什么结构",最后讲"哪些题在考这个原理"。
建议课时:6 课时。
0. 总纲:六个原理
数据结构题的难度不在于"知道多少种结构",而在于"看出题目在考哪个原理"。竞赛中所有数据结构技巧,几乎都落在下面六条之一或其组合:
| 原理 | 一句话 | 典型产物 |
|---|---|---|
| I. 信息是幺半群,标记是作用 | 能合并就能分治,能复合就能延迟 | 线段树一切变体、动态 DP、历史最值、猫树 |
| II. 势能与均摊 | 单次可能很慢,但总量有上界 | Beats、颜色段均摊、线段树合并、DSU on tree、Splay |
| III. 分治的三个方向 | 下标、时间、值域,离线后都可以分治 | CDQ、线段树分治、整体二分、二进制分组 |
| IV. 可逆与可持久 | 能撤销就能回溯,能复制路径就能保留历史 | 可撤销并查集、可持久化一切、版本差分 |
| V. 阈值与平衡 | 两种代价之积恒定时,取平方根 | 分块、根号分治、莫队及其二次离线、KD-tree |
| VI. 树到序列的映射 | 树上问题的难点只是"怎么线性化" | 重链 / 长链 / 实链剖分、点分树、DFS 序族 |
读题时的检查顺序:信息能不能合并(I)→ 操作有没有势能(II)→ 能不能离线(III)→ 需不需要历史版本(IV)→ 有没有两种代价可以平衡(V)→ 树怎么变成序列(VI)。
I. 信息是幺半群,标记是作用
I.1 为什么线段树能维护一个信息
线段树的每个节点存的是"这段区间的信息",pushup 是"两段相邻区间的信息 \(\to\) 合并后区间的信息"。这要求信息集合 \(M\) 上有一个二元运算 \(\circ\) 满足结合律:
有结合律的 \((M, \circ)\) 叫半群;再加一个单位元就是幺半群。线段树能维护的信息,恰好是半群。 不需要交换律(左右顺序是固定的),不需要逆元。
| 信息 | \(\circ\) | 是半群? | 备注 |
|---|---|---|---|
| 区间和 | \(+\) | 是 | 还是交换群 |
| 区间最值 | \(\max\) | 是 | 幂等 |
| 区间 gcd | \(\gcd\) | 是 | 幂等 |
| 最大子段和 \((s, l, r, a)\) | 见下 | 是 | 四元组,不交换 |
| 区间哈希 \((h, \text{len})\) | \(h_L B^{\text{len}_R} + h_R\) | 是 | 不交换 |
| 矩阵 | 矩阵乘 | 是 | 不交换,万能 |
| 区间"括号匹配后剩余" \((\text{左多余}, \text{右多余})\) | 抵消 | 是 | |
| 区间中位数 | 无 | 否 | 两段的中位数推不出整体,必须用值域结构 |
最大子段和:\((s_1,l_1,r_1,a_1)\circ(s_2,l_2,r_2,a_2) = (s_1+s_2,\ \max(l_1, s_1+l_2),\ \max(r_2, r_1+s_2),\ \max(a_1,a_2,r_1+l_2))\)。
判断一个信息能否用线段树维护的方法:写出"两段合并"的公式。写得出来且只依赖两段各自的信息,就能维护。写不出来(如中位数、众数),就要换一个"更大的信息"(如整个值域直方图,那就是值域线段树 / 主席树),或者换原理(II、V)。
I.2 标记是"作用",复合要有结合律
懒标记 \(t\) 是一个函数 \(M \to M\)(把区间信息映成打了标记后的信息)。多个标记先后作用,需要能复合成一个标记,且复合满足结合律。这就是说标记集合 \(T\) 在复合下也是一个幺半群,且 \(T\) 作用在 \(M\) 上。
"区间乘 \(a\) 再加 \(b\)"的标记是仿射函数 \(x \mapsto ax + b\),复合 \((a_1, b_1)\) 后 \((a_2, b_2)\) 得 \((a_1 a_2,\ a_2 b_1 + b_2)\)。这个复合有结合律,所以是合法标记。
标记要作用在整段信息上,所以标记的定义需要包含"如何作用于区间和"(用到区间长度)。这就是为什么线段树节点常要存长度。
I.3 交换律决定能否永久化
标记永久化(不下传,查询时累计路径标记)要求路径上的标记顺序可以任意交换。所以:
- 加法标记:可交换,可永久化。可持久化线段树的区间加、二维线段树、树套树的内层都靠这个。
- 赋值标记:不可交换,不能永久化(后来的赋值必须覆盖先前的)。
- 乘加标记:不可交换,不能永久化。
推论:可持久化线段树支持区间加、区间和,但不支持区间赋值(除非改用"每个版本一棵新的根到叶路径"的技巧,代价更大)。
I.4 历史最值:把"时间"编进标记
问题(洛谷 P6242 / P4314 CPU 监控):区间加,区间查询"历史上出现过的最大值"。
设每个位置 \(x\) 的当前值 \(v\)、历史最大值 \(h\)。区间加 \(a\) 后 \(v' = v + a\),\(h' = \max(h, v + a)\)。
标记定义为 \((a, b)\):当前加 \(a\),"从标记生效起到现在这段时间里的最大前缀加量"为 \(b\)。作用:\(v' = v + a\),\(h' = \max(h, v + b)\)。
复合:先 \((a_1, b_1)\) 后 \((a_2, b_2)\):
所以
单个加法 \(a\) 对应标记 \((a, a)\)(\(a>0\))或 \((a, 0)\)(历史最大不会因为减少而更新,所以 \(b = \max(a, 0)\))。
这个复合满足结合律(验证一下:本质是 \((\max, +)\) 半环上的 \(2\times 2\) 上三角矩阵乘法)。历史最值不是一个"技巧",是一个新的标记幺半群。 只要写出这个复合,剩下的就是普通线段树。
历史最小值、历史和(\(\sum\) 每时刻的值)同理,后者的标记是 \((a, \text{累计加量} \cdot \text{经过的时间})\) 的复合,需要额外记录"标记覆盖的时间长度"。
I.5 矩阵:万能标记与动态 DP
当信息是一个向量、转移是线性的(在某个半环上),标记就可以用矩阵表示,复合就是矩阵乘。
\((\max, +)\) 半环:把加法换成 \(\max\),乘法换成 \(+\),矩阵乘法 \(C_{ij} = \max_k (A_{ik} + B_{kj})\)。结合律成立,所以"DP 转移"可以当作矩阵来合并。
动态 DP(洛谷 P4719):树上最大权独立集,带单点修改点权。
普通 DP:\(f_u^0 = \sum_v \max(f_v^0, f_v^1)\),\(f_u^1 = w_u + \sum_v f_v^0\)。
重链剖分后,\(u\) 的儿子分成一个重儿子 \(h\) 和若干轻儿子。把轻儿子的贡献单独记为
则 \(f_u\) 只依赖 \(f_h\) 和 \(g_u\):
一条重链自顶向下是 \(u_1, u_2, \dots, u_k\)(\(u_k\) 是叶),链顶的 \(f\) 等于 \(G_{u_1}\otimes G_{u_2}\otimes\cdots\otimes G_{u_k}\otimes(0, 0)^T\)。用线段树维护重链上矩阵的乘积。
修改点 \(u\) 的权值:
- 更新 \(G_u\),线段树单点修改,重新算出 \(u\) 所在重链链顶的 \(f\)。
- 链顶 \(t\) 的父亲 \(p\) 的 \(g_p\) 依赖 \(f_t\)(\(t\) 是 \(p\) 的轻儿子),把旧的 \(f_t\) 从 \(g_p\) 中减掉,加上新的,更新 \(G_p\)。
- 对 \(p\) 所在重链重复。
每次修改经过 \(O(\log n)\) 条重链,每条链一次线段树操作,\(O(\log^2 n)\),矩阵 \(2\times 2\) 常数 8。
全局平衡二叉树:把每条重链不建普通线段树,而是按子树大小加权建带权平衡的二叉树(类似 Splay 的静态形态,但不旋转),并把各链的树用轻边连起来。整棵结构深度 \(O(\log n)\),修改只需从叶向上更新一遍,严格 \(O(\log n)\)。这是"静态化的 LCT",是动态 DP 的最优实现。
动态 DP 的适用范围:任何"树上 DP,状态数为常数 \(k\),转移是 \((\oplus, \otimes)\) 半环上的线性变换"的问题,包括最大独立集、最小点覆盖、最小支配集、路径计数、带权匹配等。矩阵是 \(k \times k\)。
I.6 非交换半群的 \(O(1)\) 区间查询:猫树
ST 表只能处理幂等的信息(\(\max\)、gcd)。若信息只是半群(如最大子段和、矩阵乘),且无修改,如何 \(O(1)\) 查询?
猫树:建线段树,对每个节点 \([l, r]\)(中点 \(m\))预处理
- \(\text{suf}[i] = a_i \circ a_{i+1}\circ\cdots\circ a_m\),\(i \in [l, m]\)
- \(\text{pre}[j] = a_{m+1}\circ\cdots\circ a_j\),\(j \in [m+1, r]\)
查询 \([x, y]\):找到 \(x, y\) 被分到不同儿子的那个节点(用叶子编号的最高相异位定位,\(O(1)\)),答案 \(= \text{suf}[x]\circ\text{pre}[y]\)。
预处理 \(O(n\log n)\) 次 \(\circ\),查询 \(1\) 次 \(\circ\)。代价:不支持修改。
分治 + 半群 的推广:这个"按中点劈开、两边各存前后缀"的思想也是分治 NTT、分治线段树优化 DP 的骨架。
I.7 信息依赖"左边的统计量":非标准 pushup
楼房重建(洛谷 P4198):区间内"前缀最大值的个数"。右子区间的贡献依赖左子区间的最大值。
pushup 时对右子树做一次 \(O(\log n)\) 的递归(每层只走一边),单次修改 \(O(\log^2 n)\)。原理:右子树的贡献函数 \(c(\text{lim})\) 关于门槛 \(\text{lim}\) 单调,且可以在树上二分。
同类问题:区间内"能看到的点数"、"单调栈大小"、"以 \(a_i\) 结尾的最长上升前缀",凡是右边的答案是左边某个单调量的函数、且这个函数能在线段树上递归求值的,都是这个模型。
I.8 Kinetic Segment Tree(KTT,选讲)
维护一组一次函数 \(y_i = k_i x + b_i\),操作:区间对 \(x\) 加正数(时间推进),区间查询 \(\max y_i\)。
每个节点存当前最大的那条线,以及"最快会被超越的时刻"(下一次最优线变化的 \(x\))。区间加 \(x\) 时若不超过这个时刻,直接打标记;否则递归重建。势能分析给出 \(O(n\log^2 n)\) 或 \(O(n\log^3 n)\) 均摊。这是 Beats(原理 II)与半群维护的结合体。例题:洛谷 P5693 [CSA] Kinetic Tournament。
I.9 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P4513 小白逛公园 | 最大子段和半群 |
| 洛谷 P4314 CPU 监控 | 历史最值标记复合 |
| 洛谷 P6242 线段树 3 | 历史最值 + Beats |
| 洛谷 P4719 / P4751 动态 DP | 矩阵 + 重链 / 全局平衡二叉树 |
| 洛谷 P5024 [NOIP2018] 保卫王国 | 动态 DP 应用(强制选 / 不选) |
| 洛谷 P4198 楼房重建 | 非标准 pushup |
| CF 718C Sasha and Array | 矩阵标记 |
| 洛谷 P6240 好吃的题目 | 猫树 / 分治 |
II. 势能与均摊
II.1 势能分析的模板
定义势能函数 \(\Phi(\text{状态}) \ge 0\)。一次操作的均摊代价 $= $ 实际代价 \(+ \Delta\Phi\)。若每次操作的均摊代价都 \(\le c\),则总实际代价 \(\le qc + \Phi_0 - \Phi_{\text{end}} \le qc + \Phi_0\)。
用法:找一个量,它"每次昂贵操作都会显著减少,而每次普通操作只增加常数"。
| 结构 | 势能 | 每次昂贵操作减少 | 结论 |
|---|---|---|---|
| 并查集(路径压缩 + 按秩) | 略(Tarjan) | \(O(\alpha)\) | |
| Splay | \(\sum \log \text{size}\) | \(O(\log n)\) 均摊 | |
| 线段树合并 | 总节点数 | 每递归一层减少一个节点 | 总 \(O(n\log n)\) |
| DSU on tree | 略 | 每个点被加入 \(O(\log)\) 次 | \(O(n\log n)\) |
| 区间开方 | \(\sum \log\log a_i\) | 每个非 1 元素至少减一 | \(O(n\log\log V)\) 次叶子访问 |
| 区间取模 | \(\sum \log a_i\) | 取模后至少减半 | \(O(n\log V)\) |
| 颜色段均摊 | 段数 | 区间赋值删掉 \(k\) 段只新增 \(O(1)\) 段 | 总 \(O(n + q)\) 段变化 |
| Beats(chmin) | 区间内不同值的个数之和 | 每次穿透次大值都合并两个值 | \(O((n+q)\log n)\) |
II.2 Segment Tree Beats 的完整版本与证明
操作:区间 chmin、区间 chmax、区间加、查询区间和 / 最大 / 最小 / 历史最大。
节点信息:\(\text{mx1}, \text{mx2}, \text{cmx}\)(最大值、严格次大值、最大值个数),\(\text{mn1}, \text{mn2}, \text{cmn}\),\(\text{sum}\),历史最大 \(\text{hmx}\)。
标记:对"最大值那一类"的加法 \(\text{tmx}\)、对"最小值那一类"的加法 \(\text{tmn}\)、对"其余元素"的加法 \(\text{tot}\),以及各自的历史最大版本 \(\text{htmx}, \text{htmn}, \text{htot}\)(用 I.4 的 \((a,b)\) 复合)。
下传时的关键判断:子节点的最大值是否等于父节点的最大值?相等则子节点的最大值那一类接收 \(\text{tmx}\),否则接收 \(\text{tot}\)。最小值同理。注意当 \(\text{mx1} = \text{mn1}\)(区间只有一种值)或 \(\text{mx2} = \text{mn1}\) 之类的重叠情况要特判。
chmin \(v\) 的递归条件:
- \(v \ge \text{mx1}\):无事。
- \(\text{mx2} < v < \text{mx1}\):只有最大值那一类变,打标记。
- \(v \le \text{mx2}\):递归。
复杂度证明(只有 chmin / chmax):定义每个节点的势能为"该节点区间内不同值的个数",\(\Phi = \sum_{\text{节点}}\)。初始 \(\Phi = O(n\log n)\)。每次递归到"\(v \le \text{mx2}\)"的节点,操作后原本 \(\ge \text{mx2}\) 的至少两个不同值合并为一个,该节点势能至少减 1。递归的总次数 \(\le\) 势能总减少量 \(+\) 正常的 \(O(\log n)\) 定位。所以总 \(O((n + q)\log n)\)。
加上区间加:区间加会让一个节点的势能增加(原本相等的值可能因为部分被加而分开),一次区间加影响 \(O(\log n)\) 个节点,每个节点势能最多增加 \(O(\log n)\)(可以证明),所以总 \(O((n+q)\log^2 n)\)。实际跑起来接近单 \(\log\)。
教学要点:Beats 不是"记住 mx2 < v 就打标记",而是"找一个势能,让每次深入递归都消耗它"。区间开方、取模、按位与或(下面)都是同一个模板。
II.3 区间按位或 / 按位与 + 区间加 + 区间和
问题(洛谷 P2572 / CF 1004F 类):区间 or \(v\)、and \(v\)、加、求和。
节点维护 \(\text{and}\)、\(\text{or}\)。区间 or \(v\) 时:
- 若 \((\text{and} \,\&\, v) = (\text{or}\,\&\, v)\),即 \(v\) 中为 1 的每一位,在区间内所有数上要么全 0 要么全 1:那么 or \(v\) 等价于加上 \(v \,\&\, \sim\text{and}\)(那些全 0 的位),打加法标记。
- 否则递归。
势能:区间内"不一致的位"的个数(\(\text{or} \oplus \text{and}\) 的 popcount)之和。每次递归至少使某一位从不一致变成一致,且区间加最多让 \(O(\log V)\) 位变不一致。总 \(O((n+q)\log n\log V)\)。
II.4 颜色段均摊("珂朵莉树"的正确打开方式)
用 std::set 维护值相同的极长段 \([l, r, v]\)。区间赋值:split 出 \(l\) 和 \(r+1\) 两个断点(新增至多 2 段),删掉中间所有段,插入 1 段。
势能 = 段数。初始 \(\le n\),每次操作至多 \(+3\),删除的段数被势能支付。所有操作的遍历段数总和 \(O(n + q)\)。
这意味着:只要题目的修改是"区间赋值",任何"对区间内每一段做点什么"的操作都是均摊 \(O((n+q)\cdot \text{单段代价})\)。 这是一个严格结论,不依赖数据随机。
数据随机时,即使没有区间赋值(如区间加),随机的区间赋值出现频率足够高,段数期望 \(O(\log n)\),所以 ODT 才能"乱搞"过去。
例题:CF 896C(原题)、洛谷 P8146(区间赋值 + 区间查询,配合线段树维护段内信息)。
II.5 线段树合并与线段树分裂的势能
合并:递归只在两棵树都有的节点上进行,每次递归后两个节点合成一个,总节点数减一。初始节点数 \(O(n\log n)\),所以总合并代价 \(O(n\log n)\)。
注意:如果合并后还要保留原来的两棵树(如可持久化线段树合并),复杂度分析仍然成立,但空间需要新开节点,空间 \(O(n\log n)\) 不变(合并创建的节点数 \(\le\) 递归次数)。
分裂:每次分裂新增 \(O(\log n)\) 个节点,势能增加 \(O(\log n)\),所以"合并 + 分裂"混合的总复杂度 \(O((n + q)\log n)\)。
线段树合并维护的信息:任何幺半群信息(I.1)都可以,且合并过程中可以顺便计算跨越两棵树的贡献,例如"逆序对数"(合并时左子树 \(a\) 的计数乘右子树 \(b\) 的计数)。例题:洛谷 P3521 [POI2011] ROT-Tree Rotations。
II.6 DSU on tree 与启发式合并
启发式合并:小的合并到大的。每个元素每次被移动,所在集合大小至少翻倍,所以每个元素最多被移动 \(O(\log n)\) 次。
DSU on tree 是它在树上的特例:保留重儿子的信息,暴力加入轻儿子。每个点到根的轻边数 \(O(\log n)\),所以每个点被暴力加入 \(O(\log n)\) 次。
与线段树合并的对比:DSU on tree 只能回答"子树的离线统计",且要求信息可增删(或至少可增 + 可清空);线段树合并可以在线维护子树的整棵值域结构。
\(O(n\log^2 n)\) 的启发式合并 + 平衡树 / set:合并两个 set 时把小的逐个插入大的,总 \(O(n\log^2 n)\)。常见于"合并两个连通块的有序集合"。
II.7 Splay 与 LCT 的势能
Splay 的势能 \(\Phi = \sum_x \log \text{size}(x)\)。一次 splay 均摊 \(O(\log n)\)。LCT 的 access 均摊 \(O(\log n)\) 依赖两个事实:(1)每次 access 切换的实边数均摊 \(O(\log n)\)(重轻边论证);(2)每个 splay 均摊 \(O(\log n)\)。
实践含义:LCT 不能用 Treap 替代 Splay 来维护实链,因为 access 需要 splay 的"把访问过的节点提到根"来保证均摊。
II.8 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P6242 线段树 3 | Beats 完整版 |
| 洛谷 P4145 花神游历各国 | 开方势能 |
| CF 438D The Child and Sequence | 取模势能 |
| 洛谷 P2572 [SCOI2010] 序列操作 | 位运算势能(简化版) |
| CF 896C Willem, Chtholly and Seniorious | 颜色段均摊 |
| 洛谷 P3521 ROT-Tree Rotations | 线段树合并算贡献 |
| CF 600E Lomsat gelral | DSU on tree |
| 洛谷 P5693 Kinetic Tournament | KTT |
III. 分治的三个方向
III.1 三种分治的统一视角
一个带修改、带查询的问题有三个维度:下标(位置)、时间(操作顺序)、值域(答案)。在线时三个维度都必须实时处理。离线后,可以选一个维度做分治,把它"降维"掉。
| 分治维度 | 名字 | 消掉的东西 | 代价 |
|---|---|---|---|
| 下标 / 时间(对操作序列分治) | CDQ 分治 | 一维偏序 | \(\times\log\) |
| 时间(对操作存在区间分治) | 线段树分治 | 删除操作 | \(\times\log\) |
| 值域(对答案分治) | 整体二分 | 二分的在线性 | \(\times\log V\) |
共同点:分治后,"左半对右半的贡献"变成一个更低维的问题。CDQ 把三维偏序变成二维(一维归并、一维 BIT);线段树分治把"加边 + 删边"变成"加边 + 撤销";整体二分把"每个查询二分答案"变成"所有查询共享同一次 check"。
III.2 CDQ 分治的三个层次
层次 1:统计贡献。三维偏序:第一维排序,第二维分治归并,第三维 BIT。\(O(n\log^2 n)\)。
层次 2:优化 DP。转移 \(f_i = \max_{j<i,\ (a_j, b_j) \prec (a_i, b_i)} f_j + w_i\)。分治 \([l, r]\):先递归 \([l, m]\)(此时左半的 \(f\) 已确定),用左半更新右半,再递归 \([m+1, r]\)。顺序必须是:左、跨、右,不能像统计贡献那样"左、右、跨"。
层次 3:CDQ 套 CDQ。四维偏序:第一维排序,第二维分治,"左半对右半"的贡献变成三维偏序,再套一层 CDQ。\(O(n\log^3 n)\)。写法:内层 CDQ 处理时给每个元素打上"来自外层左半 / 右半"的标记,只统计左对右。
CDQ 的限制:贡献必须可以"左对右单向计算"且可加(BIT 维护的是可减群)。若贡献是 \(\max\) 且转移不可减,用层次 2 的 DP 写法(顺序保证不需要撤销)。
III.3 线段树分治:把删除变成撤销
适用:每个"元素"(边、点、约束)有存在区间 \([l_i, r_i]\);查询在每个时刻。要求维护的结构支持撤销(IV.1),但不需要支持删除。
经典:动态图连通性(离线)、动态二分图判定、动态最大权匹配的存在性、离线动态 MST(配合 LCT 或按秩并查集)。
不止时间轴:任何"每个元素在某个区间上有效"的维度都可以做线段树分治。例如"每条边对权值在 \([a, b]\) 范围内的询问有效",就对权值轴做线段树分治。
复杂度:每个元素挂在 \(O(\log T)\) 个节点,每个节点上的元素加入一次撤销一次,总 \(O(m\log T\cdot \text{单次操作})\)。
III.4 整体二分
适用:(1)每个查询的答案可以二分;(2)check 函数对所有查询是"同一种",可以批量做;(3)修改与查询之间只有时间顺序约束。
核心:对 \([L, R]\) 值域和一批操作,用 \(\text{mid}\) 做 check 时只考虑值 \(\le \text{mid}\) 的修改,用 BIT 累计对每个查询的贡献,把查询分到左 / 右,修改也按值分到左 / 右。每个操作在每层出现一次,共 \(\log V\) 层,每层 \(O(\text{ops}\cdot\log n)\)。
与 CDQ 的区别:CDQ 对操作序列的下标分治,整体二分对答案分治。两者可以嵌套(如整体二分内部每层用 CDQ 处理三维偏序的 check)。
III.5 二进制分组:把离线结构在线化
问题:有一个只支持"离线建立、查询"的结构 \(S\)(如 KD-tree、猫树、离线 CDQ 的结果、凸包),现在要求支持在线插入。
方法:维护若干个结构,大小分别是 \(2^{k_1} > 2^{k_2} > \cdots\)(二进制表示)。插入一个元素:新建大小 1 的结构,若已有大小 1 的,合并成 2;若已有大小 2 的,合并成 4……(二进制加法进位)。查询时对所有 \(O(\log n)\) 个结构各查一次,合并答案。
代价:每个元素被重建 \(O(\log n)\) 次,查询 \(\times\log n\)。要求:查询的答案可以从各结构的答案合并(\(\max\)、\(\sum\) 等),且结构支持"从两个小的合并 / 重建成一个大的"。
不支持删除。若要删除,配合线段树分治(III.3)或"带权删除标记"。
例题:CF 710F String Set Queries(在线 AC 自动机:二进制分组维护若干自动机)、洛谷 P4585 [FJOI2015] 火星商店问题(二进制分组 / 线段树分治 + 可持久化 Trie)。
III.6 点分治:树上的下标分治
树上"经过某点的路径"是"跨越中点的区间"的类比。重心保证递归 \(O(\log n)\) 层。
边分治:找一条边使两侧尽量平衡。原树度数无界时先"三度化"(每个点拆成一条链,新边权 0)。边分治的好处:每层只有两侧,"跨越"的贡献只需两个集合配对,某些信息(如两侧各建一棵结构)比点分治的多子树容斥好写。
点分树(IV 和 VI 的组合):把点分治的分治结构本身建成树,每层的信息用数据结构维护,从而支持修改。
III.7 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P3810 三维偏序 | CDQ 层次 1 |
| 洛谷 P4093 [HEOI2016] 序列 | CDQ 优化 DP |
| 洛谷 P3157 动态逆序对 | CDQ / 树套树 |
| CF 762E Radio stations | CDQ 或 线段树分治 |
| 洛谷 P5787 二分图 | 线段树分治 |
| 洛谷 P4219 [BJOI2014] 大融合 | 线段树分治 / LCT |
| 洛谷 P2617 Dynamic Rankings | 整体二分 |
| 洛谷 P3332 [ZJOI2013] K 大数查询 | 整体二分 + 区间修改 |
| CF 710F String Set Queries | 二进制分组 |
| 洛谷 P4585 火星商店问题 | 线段树分治 + 可持久化 Trie |
| 洛谷 P4292 [WC2010] 重建计划 | 点分治 + 单调队列 |
IV. 可逆与可持久
IV.1 撤销的条件
一个操作能撤销,当且仅当能在 \(O(\text{操作代价})\) 内恢复操作前的状态。做法是记录"改了什么",压栈;撤销时弹栈。
关键限制:撤销必须按栈序(后进先出)。 这意味着:
- 线段树分治(DFS 序天然是栈序)可以用。
- "删除任意一个早先加入的元素"不能用。
可撤销的结构:按秩合并并查集(不能路径压缩,因为路径压缩改变的节点数无界)、线段树(标记永久化或记录修改)、Trie、任何"每次操作只改 \(O(\log)\) 个位置"的结构。
不可撤销的结构:路径压缩并查集、Splay(旋转改变太多)、哈希表的重建。
IV.2 可持久化的本质:路径复制
可持久化 = 每次修改后旧版本仍可访问。对树形结构(线段树、Treap、Trie),修改只影响根到某叶的一条路径,复制这条路径(\(O(\log n)\) 个节点),其余节点共享。
条件:结构是树形,且修改只走一条(或 \(O(\log)\) 条)路径。
线段树:单点修改天然 \(O(\log n)\)。区间修改需要标记永久化(I.3),否则下传会复制两个儿子。
Treap:split / merge 中经过的节点全部复制。随机优先级存在节点里,复制时一起复制。注意:同一个节点在两个版本中共享时,不能在它上面做原地 pushdown,必须先复制再 pushdown。
平衡树的可持久化必须用 Treap(FHQ)而不是 Splay,原因同 IV.1:Splay 的旋转改变的节点数无界。
并查集:可持久化数组存 \(\text{fa}\),按秩合并不压缩,\(O(\log^2 n)\)。
IV.3 版本差分:信息是可减群时
主席树求区间第 \(k\) 小的本质:版本 \(r\) 的树减版本 \(l-1\) 的树。这要求节点信息是群(有逆元),计数是整数加法群,可以。
推广:任何"前缀可持久化 + 信息可减"的问题都可以用版本差分做区间查询:区间不同数(用"最后出现位置"编码)、区间异或第 \(k\) 大(可持久化 Trie)、区间 \(\text{mex}\)(值域上维护"最后出现位置",查最小的"最后位置 \(< l\)"的值)。
树上版本差分:以 DFS 顺序建版本(每个点的版本 = 父亲版本 + 该点),路径 \(u \to v\) 的信息 \(= T_u + T_v - T_{\text{lca}} - T_{\text{fa}(\text{lca})}\)。例题:洛谷 P2633 Count on a tree。
IV.4 可持久化线段树合并
线段树合并时,被合并的两棵树在新版本中不再需要,可以原地合并;但若需要保留旧版本,就要在合并时新建节点而不是复用 \(a\)。
复杂度:递归次数分析(II.5)不变,所以新建节点数 \(O(n\log n)\)。空间通常开到 \(n\log n \times\) 常数。
例题:洛谷 P3302 [SDOI2013] 森林(可持久化线段树 + 启发式合并 + 树上主席树)。
IV.5 时间倒流
把操作序列反过来做:删除变插入,"何时第一次断开"变"何时最后一次连上"。配合并查集能解决很多"离线删除"问题(如 CF 1213G、洛谷 P1197 [JSOI2008] 星球大战)。
本质:并查集只支持加边不支持删边,而时间倒流把删边序列变成了加边序列。这是"利用离线消除不可逆操作"的最简单例子,与线段树分治(III.3)目的相同,但只适用于"操作序列可完全逆转"的情形。
IV.6 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P3919 / P3834 可持久化线段树 1 / 2 | 基础 |
| 洛谷 P3402 可持久化并查集 | 可持久化数组 |
| 洛谷 P5055 可持久化文艺平衡树 | 可持久化 Treap 带标记 |
| 洛谷 P4735 最大异或和 | 可持久化 Trie |
| 洛谷 P2633 Count on a tree | 树上版本差分 |
| 洛谷 P4137 Rmq Problem / mex | 值域上"最后出现位置" |
| 洛谷 P3302 [SDOI2013] 森林 | 可持久化 + 启发式合并 |
| 洛谷 P1197 星球大战 | 时间倒流 |
V. 阈值与平衡
V.1 平方根的来源
两种代价 \(A(B)\) 和 \(C(n/B)\),当乘积恒定时,\(B = \sqrt{n}\) 最小化和。更一般地,若操作 1 有 \(q_1\) 次代价 \(O(B)\),操作 2 有 \(q_2\) 次代价 \(O(n/B)\),最优 \(B = \sqrt{nq_2/q_1}\)。
根号算法的本质:把问题按某个量 \(x\) 分成"\(x \le B\)"和"\(x > B\)",前者预处理 / 打表,后者暴力,两边复杂度相等时最优。
V.2 序列分块与值域分块的组合
序列分块:整块打标记、散块暴力。
值域分块:值域 \([1, V]\) 分 \(\sqrt V\) 块,插入删除 \(O(1)\),查询前缀 / 第 \(k\) 小 \(O(\sqrt V)\)。
为什么值域分块比树状数组更适合莫队:莫队移动指针 \(O(n\sqrt q)\) 次(多),查询 \(q\) 次(少)。值域分块让"多的操作" \(O(1)\)、"少的操作" \(O(\sqrt V)\),总 \(O(n\sqrt q + q\sqrt V)\);树状数组是两边都 \(\log\),总 \(O(n\sqrt q\log n)\)。"多的操作便宜,少的操作贵"是根号平衡的通用原则。
V.3 分块预处理:区间众数
问题(洛谷 P4168 蒲公英):静态区间众数。
- 预处理 \(f[i][j]\) = 第 \(i\) 到第 \(j\) 块的众数,\(O(n\sqrt n)\)。
- 预处理每个值的出现位置列表。
- 查询:整块部分的众数是候选之一;散块中每个值也是候选,对每个候选用出现位置列表二分求区间内出现次数,\(O(\sqrt n\log n)\)。
优化到 \(O(\sqrt n)\):预处理 \(\text{cnt}[i][v]\) = 前 \(i\) 块中 \(v\) 的出现次数(空间 \(O(n\sqrt n)\),可用 short 或滚动),散块暴力时直接查。
回滚莫队做同一问题 \(O(n\sqrt n)\),但空间 \(O(n)\)。
V.4 莫队二次离线
问题(洛谷 P4887):区间内满足 \(\text{popcount}(a_i \oplus a_j) = k\) 的点对数。
普通莫队每次移动指针 \(x\) 需要求"\([l, r]\) 中有多少个 \(a_j\) 与 \(a_x\) 异或后 popcount 为 \(k\)",这需要 \(O(\binom{14}{k})\),总 \(O(n\sqrt n\cdot\binom{14}{k})\) 太慢。
二次离线:把每次指针移动的贡献 \(f(x, [l, r])\) 拆成前缀差 \(f(x, [1, r]) - f(x, [1, l-1])\)。指针从 \(r\) 移到 \(r'\) 时需要 \(\sum_{x=r+1}^{r'} f(x, [1, x-1])\) 这一类(只依赖 \(x\),\(O(n)\) 预处理)减去 \(\sum_{x=r+1}^{r'} f(x, [1, l-1])\)(依赖 \((x, l-1)\),把 \((x\) 的区间 \([r+1, r'],\ l-1)\) 作为一个"二次询问"存下来)。
二次询问只有 \(O(q)\) 个,但每个涉及 \(O(\sqrt n)\) 长度的 \(x\) 区间。按 \(l-1\) 排序后扫描,扫到位置 \(p\) 时把 \(a_p\) 加入一个 \(O(\binom{14}{k})\) 更新、\(O(1)\) 查询的结构,然后回答所有"右端为 \(p\)"的二次询问,每个二次询问 \(O(\sqrt n)\)。
总 \(O(n\binom{14}{k} + n\sqrt q)\)。
本质:莫队把"一次询问"变成 \(O(\sqrt n)\) 次"指针移动",二次离线再把"指针移动的贡献"变成"前缀贡献之差",后者可以按另一个维度离线。这是"离线套离线"。 同样的框架解决区间逆序对 \(O(n\sqrt n)\)(洛谷 P5047)。
V.5 根号分治的常见形态
| 分治量 | 小的一边 | 大的一边 | 例 |
|---|---|---|---|
| 模数 \(x\) | 预处理 \(f[x][y]\) | 暴力跳 \(n/x\) 步 | 哈希冲突 P3396 |
| 度数 | 暴力枚举邻居 | 大点少,每个单独维护 | 三元环 P1989、CF 1039D |
| 出现次数 | 暴力扫描所有位置 | 值少,两两配对预处理 | 出现次数 \(\ge \sqrt n\) 的值不超过 \(\sqrt n\) 个 |
| 跳跃步长 | 预处理每个起点的答案 | 暴力跳 | 弹飞绵羊的根号做法 |
| 询问区间长度 | 短区间暴力 | 长区间用重结构 |
V.6 分块 + bitset
问题:区间"是否存在两个数之和 / 之差 / 之积 为 \(v\)"。
把区间内的值集合压成 bitset,\(a + b = v\) 转化为 \(\text{bs} \,\&\, (\text{rev}(\text{bs}) \gg (V - v))\) 非空。莫队维护 bitset,\(O(n\sqrt q + qV/64)\)。例题:洛谷 P3674 小清新人渣的本愿。
分块 + bitset 判集合相等:每块预处理 bitset,询问时整块 or 起来,散块单独加,\(O((n/B + B)V/64)\)。
V.7 KD-tree 的复杂度
结构:交替按 \(x\)、\(y\) 中位数分割的二叉树。
矩形查询 \(O(\sqrt n)\) 的证明:矩形的一条边(一条直线)与 KD-tree 的区域相交时,每两层(一次 \(x\) 分割、一次 \(y\) 分割)把区域分成 4 块,一条直线最多穿过其中 2 块。所以 \(T(n) = 2T(n/4) + O(1) = O(\sqrt n)\)。四条边总共 \(O(\sqrt n)\)。
\(k\) 维:\(O(n^{1 - 1/k})\)。三维已经是 \(O(n^{2/3})\),很少用。
插入:用替罪羊式重构(子树不平衡到 \(\alpha\) 比例时整体重建),均摊 \(O(\log^2 n)\)。
KD-tree 做最近点对是启发式,最坏 \(O(n)\),不要在对抗性数据上依赖它。
KD-tree 的定位:二维数点带修改、或"矩形加 + 单点查询"时,是树套树之外的另一选择,代码短,复杂度 \(O(\sqrt n)\) 而不是 \(\log^2\)。
V.8 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P4168 蒲公英 | 分块众数 |
| 洛谷 P5906 回滚莫队 | 回滚 |
| 洛谷 P4887 莫队二次离线 | 二次离线 |
| 洛谷 P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II | 二次离线逆序对 |
| 洛谷 P3396 哈希冲突 | 根号分治 |
| 洛谷 P1989 无向图三元环计数 | 度数分治 |
| 洛谷 P3674 小清新人渣的本愿 | 莫队 + bitset |
| 洛谷 P4148 简单题 | KD-tree 带插入 |
| 洛谷 P4390 [BalkanOI2007] Mokia | KD-tree / CDQ |
| 洛谷 P3203 [HNOI2010] 弹飞绵羊 | 分块 / LCT |
| 洛谷 P5356 [Ynoi2017] 由乃打扑克 | 分块 + 块内排序 |
VI. 树到序列的映射
VI.1 四种序
| 序 | 性质 | 用途 |
|---|---|---|
| DFS 序(每点一次) | 子树 = 连续区间 | 子树修改 / 查询 |
| 欧拉序(每点进出各一次,\(2n\)) | 路径 \(u\to v\) = 区间内出现奇数次的点 | 树上莫队 |
| 欧拉序(每次访问都记,\(2n-1\)) | LCA = 区间深度最小的点 | ST 表 \(O(1)\) LCA |
| 重链剖分序 | 子树连续 且 重链连续 | 路径 = \(O(\log n)\) 段 |
重链剖分序同时满足子树连续和链连续,这是它优于其他序的原因,也是为什么树剖能同时做子树操作和路径操作。
VI.2 三种剖分的本质
重链剖分:按子树大小选重儿子。每走一条轻边子树大小至少减半 \(\Rightarrow\) 根到任意点的轻边数 \(\le \log n\)。优化目标:路径经过的链数。
长链剖分:按子树深度选重儿子。所有长链长度之和 \(= n\);从任意点跳到所在长链顶,再跳一次到父亲,链顶深度至少翻倍(长链性质)\(\Rightarrow\) 跳 \(O(\sqrt n)\) 次;但\(k\) 级祖先 \(O(1)\)(先倍增跳 \(2^{\lfloor\log k\rfloor}\),落到的链长 \(\ge 2^{\lfloor\log k\rfloor} \ge\) 剩余步数)。优化目标:与深度相关的合并。
实链剖分(LCT):实边可以任意变化,每条实链用 Splay 维护,均摊 \(O(\log n)\)。优化目标:动态。
全局平衡二叉树:静态版 LCT。每条重链按子树大小带权建平衡二叉树,深度严格 \(O(\log n)\)。优化目标:去掉 Splay 的均摊和 LCT 的常数,用于动态 DP。
VI.3 长链剖分优化 DP 的实现细节
\(f[u][d]\) = 子树 \(u\) 中深度为 \(d\)(相对 \(u\))的信息。
- 长儿子 \(h\) 的数组 \(f[h][\cdot]\) 通过指针偏移变成 \(f[u][\cdot + 1]\),\(O(1)\)。
- 短儿子 \(v\) 的数组暴力合并进 \(f[u]\),代价 \(O(\text{len}(v))\)。
- 每条长链只会作为"短儿子"被合并一次(在链顶的父亲处),总 \(O(\sum \text{len}) = O(n)\)。
内存分配:对每条长链分配一段长为链长的连续内存,链上每个点指向这段内存的不同偏移。
例题:CF 1009F Dominant Indices(子树中数量最多的深度)、洛谷 P5904 Hotels 加强版(三个点两两距离相等的三元组数,\(f[u][d]\) 和 \(g[u][d]\) 两个数组)。
VI.4 点分树的信息组织
点分树深度 \(O(\log n)\)。任意两点 \(u, v\) 的原树路径必经过它们在点分树上的 LCA \(c\),且 \(\text{dist}(u,v) = \text{dist}(u,c) + \text{dist}(c,v)\)。
每个点 \(c\) 维护两个结构:
- \(A_c\):\(c\) 的分治块内所有点,按 \(\text{dist}(x, c)\) 为下标的信息(如树状数组 / 动态开点线段树上的点权和)。
- \(B_c\):同样的点,但按 \(\text{dist}(x, \text{fa}(c))\) 为下标(用于在父亲处去重)。
查询"到 \(u\) 距离 \(\le k\) 的点权和":\(u\) 在点分树上的所有祖先 \(c\)(含自己),累加 \(A_c[\le k - \text{dist}(u, c)]\),减去 \(B_{c'}[\le k - \text{dist}(u, c)]\),其中 \(c'\) 是 \(c\) 的儿子且是 \(u\) 的祖先(这部分在 \(A_{c'}\) 已经算过)。\(O(\log^2 n)\)。
修改 \(u\) 的点权:对 \(u\) 的每个祖先 \(c\) 更新 \(A_c\) 和 \(B_c\)。\(O(\log^2 n)\)。
空间:每个点在 \(O(\log n)\) 个祖先的结构里出现,用动态开点 \(O(n\log^2 n)\),或对每个 \(c\) 开长度为块直径的数组,总 \(O(n\log n)\)。
\(\text{dist}(u, c)\) 的计算:预处理时对每个 \(c\) 存下块内所有点到它的距离(DFS 一次),空间同上。不要在线用 LCA 算,常数大。
VI.5 LCT 维护子树信息
LCT 的 Splay 只维护实链,虚子树的信息必须单独累加。每个点存 \(\text{vir}[x]\) = 所有虚儿子的子树信息之和,\(\text{sum}[x] = \text{sum}[\text{左}] + \text{sum}[\text{右}] + \text{vir}[x] + \text{val}[x]\)。
\(\text{vir}\) 在何时变化:只有 access 中"换右儿子"的那一步(旧右儿子变虚,新右儿子变实)和 link 时(新儿子变虚)。要求信息可减(群),否则要用可删除的结构(如平衡树、multiset)维护 \(\text{vir}\)。
例题:洛谷 P4219 大融合(子树大小)、洛谷 P3703 [SDOI2017] 树点涂色(LCT 的 access 操作本身就是"路径染新色",用线段树维护 DFS 序上的颜色数变化)。
VI.6 LCT 维护 MST 与"边化点"
要在 LCT 上维护边权信息,把每条边拆成一个点(权值为边权),连接两个端点。\(\text{cut}\) 一条边就是 cut 两次。
动态 MST:按边权排序依次加边,若两端已连通,查询路径上最大边权点,若大于当前边则 cut 掉那条边、link 新边。
最小差值生成树(洛谷 P4234):边按权排序,滑动窗口。加入新边时,若成环则删环上最小边(用 LCT 找);维护当前生成树的最小边(multiset),窗口右移时更新答案。
VI.7 虚树
只保留 \(k\) 个关键点及其两两 LCA(共 \(\le 2k - 1\) 个点),按 DFS 序排序后用栈 \(O(k\log n)\) 建树。虚树上的边权是原树路径的信息(距离、最小边权等,用倍增或树剖预处理)。
适用:多次询问,每次给一个点集,做一遍 \(O(\text{点集大小})\) 的树 DP。总 \(O(\sum k\log n)\)。例题:洛谷 P2495 [SDOI2011] 消耗战。
VI.8 树上启发式合并 vs 线段树合并 vs 长链剖分 vs 点分治
| 问题特征 | 用什么 |
|---|---|
| 子树内统计,离线,信息可加删 | DSU on tree |
| 子树内维护值域结构,在线 | 线段树合并 |
| 子树内按深度统计 | 长链剖分 |
| 路径统计(两点之间) | 点分治 |
| 路径统计 + 修改 | 点分树 |
| 路径修改 / 查询,信息是半群 | 树剖 + 线段树 |
| 动态加边删边 | LCT |
| 树上 DP 带单点修改 | 动态 DP(树剖 / 全局平衡) |
VI.9 本节题单
| 题 | 考什么 |
|---|---|
| 洛谷 P5903 树上 k 级祖先 | 长链剖分 |
| CF 1009F Dominant Indices | 长链剖分 DP |
| 洛谷 P5904 Hotels 加强版 | 长链剖分双数组 |
| 洛谷 P6329 点分树 | 点分树模板 |
| 洛谷 P3241 [HNOI2014] 开店 | 点分树 + 可持久化 |
| 洛谷 P4219 大融合 | LCT 子树信息 |
| 洛谷 P3703 树点涂色 | LCT access 的本质 |
| 洛谷 P4234 最小差值生成树 | LCT 维护 MST |
| 洛谷 P2495 消耗战 | 虚树 |
| 洛谷 P4751 动态 DP 加强版 | 全局平衡二叉树 |
| SP10707 COT2 | 树上莫队 |
VII. 数据结构优化 DP 的全景
DP 优化的本质是"转移方程 \(f_i = \text{opt}_j (f_j + w(j, i))\) 中,把对 \(j\) 的枚举换成数据结构查询"。按 \(w(j, i)\) 的形态分类:
| \(w(j,i)\) 的形态 | 结构 | 复杂度 |
|---|---|---|
| 只依赖 \(j\),且 \(j\) 的范围是区间 | 线段树 / 单调队列 | \(\log\) / \(1\) |
| \(k_j \cdot x_i + b_j\)(一次函数) | 李超树(任意顺序)/ 斜率优化(单调) | \(\log\) / \(1\) |
| 凸函数、决策单调 | 分治 / 二分队列 | \(\log\) |
| 依赖 \(j\) 的多个属性满足偏序 | CDQ | \(\log^2\) |
| \(f\) 的转移是线性变换 | 矩阵 + 线段树(动态 DP) | \(\log^2\) |
| 转移来自区间的每个点("区间到点") | 线段树优化建图 | 建图 \(n\log n\) 条边 |
| \(w\) 是区间信息且随 \(i\) 单调变化 | 单调栈 + 线段树("历史"技巧) | \(\log\) |
VII.1 线段树优化建图
从点 \(u\) 向区间 \([l, r]\) 中的每个点连边:建一棵"出树"(父连子,边权 0),\(u\) 向覆盖 \([l, r]\) 的 \(O(\log n)\) 个节点连边。从区间向点连边用"入树"(子连父)。区间向区间连边用一个虚点中转。总边数 \(O(n\log n)\)。
用途:最短路、拓扑、2-SAT 中的"区间约束"。例题:CF 786B Legacy、洛谷 P6348 [PA2011] Journeys。
VII.2 决策单调性 + 数据结构
当 \(w\) 满足四边形不等式但不能 \(O(1)\) 计算(例如 \(w(j,i)\) 是区间 \([j, i]\) 内的不同数个数),用分治求决策点,并像莫队一样移动指针维护 \(w\)。每层分治指针移动 \(O(n)\),总 \(O(n\log n\cdot\text{单次移动代价})\)。例题:CF 868F Yet Another Minimization Problem。
VII.3 平衡树维护凸包 / 斜率优化的在线版本
斜率优化要求 \(x_j\) 单调插入、查询斜率单调,否则用:
- 李超树:插入任意直线,查任意 \(x\),\(O(\log n)\)。适合"最值"。
- 平衡树 / set 维护凸包:插入任意点,动态维护上凸壳,查询时在凸壳上二分。适合需要"凸壳本身"的场景。
- CDQ 分治:离线,左半建凸壳,右半查询,\(O(n\log^2 n)\)。
VIII. 经典问题的做法谱系
同一个问题的多种做法,对比它们分别用了哪个原理。
区间第 \(k\) 小
| 做法 | 原理 | 带修? | 复杂度 |
|---|---|---|---|
| 主席树 | IV 版本差分 | 否 | \(\log n\) |
| 整体二分 | III 值域分治 | 是(离线) | \(\log n\log V\) |
| 树状数组套主席树 | I + IV | 是 | \(\log^2 n\) |
| 分块 + 值域分块 | V | 是 | \(\sqrt n\) |
区间众数:分块预处理(V.3)/ 回滚莫队(II + V)/ 强制在线用分块 + 出现位置二分。
区间不同数个数:离线 BIT("最后出现位置")/ 主席树在线 / 莫队。
区间逆序对:CDQ(离线,静态)\(n\log^2 n\) / 莫队二次离线 \(n\sqrt n\) / 分块 + 块内排序 \(n\sqrt{n\log n}\)。
区间 mex:主席树(IV.3)/ 回滚莫队 / 分块。
动态逆序对:CDQ(时间作为一维)/ 树套树。
树上路径第 \(k\) 小:树上主席树(IV.3)/ 点分治 + 整体二分 / 树剖 + 树套树。
动态图连通性:线段树分治 + 可撤销并查集(离线)/ LCT(只能处理森林)/ 在线一般图用 Holm–de Lichtenberg–Thorup(竞赛不考)。
IX. 常见错误清单
- 把非半群的信息塞进线段树。 中位数、众数不能合并,必须换值域结构或根号。
- 历史最值标记直接用 \(\max\) 累加。 必须用 \((a, b)\) 二元组复合,\(b = \max(b_1, a_1 + b_2)\)。
- 动态 DP 的矩阵乘顺序反了。 链顶在左,叶在右,\(G_{u_1}\otimes\cdots\otimes G_{u_k}\)。
- Beats 没有维护严格次大值就打标记。 \(\text{mx2} = \text{mx1}\) 时必须合并计数。
- CDQ 优化 DP 用了"左、右、跨"的顺序。 必须"左、跨、右"。
- 线段树分治用了路径压缩并查集。 不可撤销。
- 可持久化 Treap 原地 pushdown。 共享节点必须先复制。
- 可持久化线段树做区间赋值。 赋值标记不可交换,不能永久化。
- 二进制分组期望支持删除。 不支持,要配线段树分治。
- 莫队块长写死 \(\sqrt n\)。 最优 \(n/\sqrt q\);带修 \(n^{2/3}\);二次离线中的块长同普通莫队。
- 点分树查询忘记减去儿子方向的重复。 \(B_{c'}\) 那一项。
- LCT 维护子树信息时 \(\text{vir}\) 更新漏了
link。 access 和 link 两处都要改。 - 长链剖分内存分配没按链分配连续段。 指针偏移要求同链连续。
- KD-tree 用于最近点对当成严格复杂度。 是启发式。
- 颜色段均摊用于没有区间赋值的题。 段数不减,复杂度错。
X. 课堂练习
- 证明历史最值标记的复合 \((a_1,b_1)\circ(a_2,b_2) = (a_1+a_2, \max(b_1, a_1+b_2))\) 满足结合律。
- 写出"历史和"(每个位置的值在每个时刻的累加)的标记及其复合。(提示:标记 \((a, s, t)\):加量、历史和增量、时间长度。)
- 把树上最小点覆盖写成动态 DP 的 \((\min, +)\) 矩阵。
- 证明 Beats 只有 chmin 时势能 \(\Phi = \sum_{\text{节点}}(\text{不同值个数})\) 初始为 \(O(n\log n)\),且每次穿透至少减 1。
- 给出一个信息是半群但不是交换半群的例子,并说明为什么它不能标记永久化。
- 用 CDQ 套 CDQ 的框架写出四维偏序的伪代码,标出哪里需要"来源标记"。
- 说明为什么线段树分治可以处理"删除"而二进制分组不能。
- 推导莫队二次离线中"\(\sum_{x=r+1}^{r'} f(x, [1, x-1])\)"为什么可以 \(O(n)\) 预处理。
- 证明 KD-tree 矩形查询 \(T(n) = 2T(n/4) + O(1)\)。
- 对点分树,写出"修改点权"时需要更新的所有位置。
- 长链剖分 \(k\) 级祖先:为什么先跳 \(2^{\lfloor\log k\rfloor}\) 后落到的链长一定 \(\ge\) 剩余步数?
- 给出一道题,它同时用到原理 III(分治)和原理 IV(可持久化),说明各自解决什么。(答:火星商店问题。)
XI. 综合题单
| 难度 | 题目 | 原理 |
|---|---|---|
| 省选 | 洛谷 P4314 CPU 监控 | I |
| 省选 | 洛谷 P6242 线段树 3 | I + II |
| 省选 | 洛谷 P4719 动态 DP | I + VI |
| 省选 | 洛谷 P5024 保卫王国 | I + VI |
| 省选 | 洛谷 P3521 ROT-Tree Rotations | II |
| 省选 | 洛谷 P3332 K 大数查询 | III |
| 省选 | 洛谷 P4585 火星商店问题 | III + IV |
| 省选 | 洛谷 P3302 森林 | IV + II |
| 省选 | 洛谷 P4887 莫队二次离线 | V |
| 省选 | 洛谷 P3241 开店 | VI + IV |
| 省选 | 洛谷 P4219 大融合 | VI |
| 省选 | CF 786B Legacy | VII |
| NOI | 洛谷 P4751 动态 DP 加强版 | I + VI 全局平衡 |
| NOI | 洛谷 P5047 Yuno loves sqrt technology II | V 二次离线 |
| NOI | 洛谷 P5693 Kinetic Tournament | I + II KTT |
| NOI | 洛谷 P3703 树点涂色 | VI LCT access 本质 |
| NOI | 洛谷 P4292 重建计划 | III 点分治 + 二分 |
| NOI | 洛谷 P5356 由乃打扑克 | V 分块极限 |
| NOI | 洛谷 P4117 [Ynoi2018] 五彩斑斓的世界 | II + V 势能分块 |
| NOI | 洛谷 P4690 [Ynoi2016] 镜中的昆虫 | II 颜色段均摊 + 三维偏序 |
附录:一页纸总结
I. 半群与作用:信息能写出"两段合并"就能上线段树;标记能写出"两次复合"就能延迟;复合可交换才能永久化;转移是线性的就用矩阵;无修改的半群用猫树 \(O(1)\)。
II. 势能:找一个只减不增的量,让每次深入递归都消耗它。Beats 用不同值个数,开方用 \(\log\log\),取模用 \(\log\),颜色段用段数,合并用节点数,DSU on tree 用轻边数。
III. 分治:下标(CDQ)、时间(线段树分治)、值域(整体二分)三选一离线;在线化用二进制分组;树上用点分治。
IV. 可逆:按秩不压缩可撤销;树形结构路径复制可持久化;信息是群才能版本差分;操作可逆转时时间倒流。
V. 平衡:\(B = \sqrt{nq_2/q_1}\);多的操作便宜少的操作贵;离线套离线(二次离线);KD-tree 是 \(O(\sqrt n)\) 不是 \(\log\)。
VI. 树:重链管路径段数,长链管深度合并,实链管动态,全局平衡管严格 \(\log\);点分树把路径问题变成 \(O(\log)\) 个祖先的查询。
看题顺序:合并?→ 势能?→ 离线?→ 历史?→ 平衡?→ 线性化?

浙公网安备 33010602011771号