树的序列
dfs序
性质
反映的是点的遍历过程
-
任何一颗子树在序列上都是连续的
-
父亲与他的第一个儿子是连续的 (树链剖分)
树上操作转序列操作
- 单点修改, 子树查询/修改
根据第一个性质, 子树是连续的一段区间, 所以就是单点修改, 区间查询, 区间修改
- 链加, 子树求和, 单点查询
链加可以拆成三段
单点查询就是树上差分, 一次链加对一个点有贡献当且仅当, 链的低端在当前点的子树
子树求和 \(u\) 对 \(v\) 有贡献可以写成: \((dep[v] - dep[u]) \times val_u\) 乘开, 分别维护 \(val\)之和, \(val_x \times dep[x]\) , 就又变成了单点修改, 子树求和
- 子树修改, 单点修改, 链查询
链查询也是先拆开
先考虑单点修改对链的影响, 发现就是一个子树, 就变成了子树加, 单点查的问题
\(u\) 子树修改对 \(v\) 链的影响也可以写成 \((dep[v] - dep[u]) \times val_u\) 然后和上边就又一样了, 拆开维护
树上依赖性背包
见树形 \(DP\)
欧拉序
入栈时加入 \(u\), 出栈时加入 \(fa_u\).
性质
反映的是边的遍历过程
-
任何两个相邻的点都是一条边
-
两个点 \(u, v\) , 有 \(LCA(u, v) = [first_u, first_v]\)区间内深度最小的点
这段区间包含有 \(u\) 到 \(v\) 的路径, 取深度最小就是 \(LCA\)
O(1)求LCA
当然预处理是 \(O(n log_n)\)
用第二个性质做 \(rmq\)
括号序
\(dfs\) 入栈时加入左括号, 出栈时加入右括号, 分别记为 \(l_u, r_u\).
性质
反映的是 \(dfs\) 中栈的变化
-
若 \(l_u < l_v < r_v < r_u\) 则 \(u\) 是 \(v\) 的祖先
-
区间\((l_u, l_v]\) 中未匹配的括号序列个数就是 \(u, v\) 树上的距离, 最终括号序列形如\())))((((\) 拐点就是 \(lca\)
这个左开右闭的区间可以使用在\(l_u\) 右边插入一个特殊点, 来询问两个询问点之间来实现这个操作, 我们也可以对这个询问点进行一些特殊操作让他不能选择, 具体问题具体分析
- 当 \(v = 1\) 时 \(((((\) 不存在, 根据这一点, 令 \(l_u\) 处加一, \(r_u\) 处减一, 求根到 \(u\) 的链的和, 就是 \([1, l_u]\) 的和
[ZJOI2007] 捉迷藏
最远路径, 可以考虑使用括号序, 问题就转换成了, 一个区间括号匹配完之后剩余括号最多
区间? 带修改? 考虑使用最大子段和的思路
难题就转换成了, 怎么合并?
维护整个合并完之后的 \()\) 括号个数 \(a\) 和 \((\) 括号个数 \(b\) 和答案
考虑两个答案怎么合并 \())))(((( | )))((((\)
显然合并完之后是 \(a + |A - b| + B\)
绝对值怎么办? 分类讨论
大力展开 \(max(a-b+A+B, a+b+B-A)\)
分别维护 后缀 \(a+b, a-b\) 最大值, 前缀 \(a+b, b-a\) 最大值
转移这个, 也只需要带着绝对值大力展开就可以了, 神奇的发现, 我们需要的值也都维护着
合并函数:
// a ) b (
// lmax1 前缀 b-a lmax2 前缀 a+b
// rmax1 后缀 a-b rmax2 后缀 a+b
void update (int pos) {
int lson = (pos << 1), rson = (pos << 1 | 1);
a[pos] = a[lson];
b[pos] = b[rson];
if (a[rson] > b[lson]) a[pos] = a[pos] + a[rson] - b[lson];
else b[pos] = b[pos] + b[lson] - a[rson];
lmax1[pos] = std::max(lmax1[lson], b[lson] - a[lson] + lmax1[rson]);
lmax2[pos] = std::max(lmax2[lson], std::max(lmax1[rson] + a[lson] + b[lson], a[lson] - b[lson] + lmax2[rson]));
rmax1[pos] = std::max(rmax1[rson], a[rson] - b[rson] + rmax1[lson]);
rmax2[pos] = std::max(rmax2[rson], std::max(rmax1[lson] + a[rson] + b[rson], b[rson] - a[rson] + rmax2[lson]));
ans[pos] = std::max(std::max(ans[lson], ans[rson]), std::max(rmax2[lson] + lmax1[rson], rmax1[lson] + lmax2[rson]));
}
至于不能选的, 可以设值为 \(-inf\) 就可以了
树上莫队
用于求解若干次询问形如 \(u\) 到 \(v\) 路径上的问题
我们考虑怎么把这个映射到序列上, 然后使用普通莫队冲爆他
\(u\) 到 \(v\) 路径? 可以使用括号序列
一段区间如果点 \(x\) 出现一次就记上, 出现两次就不计
首先分类讨论一下
-
\(v\) 是 \(u\) 的子树, 用 \(first_u - first_v\) 表示
-
\(v\) 不是 \(u\) 的子树, 用 \(end_u - first_v\) 表示, 但这样会丢掉 \(lca\) 所以需要求一下 \(lca\) 进行特判
出栈序
按照出栈顺序构成的序列
性质
如果要求 自己的某一祖先到自己的路径上的信息, 可以用这一优美的条件, 来很轻松的完成它
我们在 一边 \(dfs\) 一边 加入, 当遍历到这个节点时, 祖先的出栈序 到自己的出栈序就是我们查询的区间