树的序列

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\) 进行特判

例题: COT2 - Count on a tree II

出栈序

按照出栈顺序构成的序列

性质

如果要求 自己的某一祖先到自己的路径上的信息, 可以用这一优美的条件, 来很轻松的完成它

我们在 一边 \(dfs\) 一边 加入, 当遍历到这个节点时, 祖先的出栈序 到自己的出栈序就是我们查询的区间

[NOI2014] 购票

posted @ 2025-02-16 19:33  d3genera7e  阅读(14)  评论(0)    收藏  举报