DFS 序
最近接触到一些 DFS 序的题,它可以用来解决一些关于子树的问题。
DFS 序本质就是一棵树在深度优先搜索时访问节点的顺序。比如有下面一棵树,其 DFS 序就是 $1 \; 2 \; 4 \; 7 \; 8 \; 5 \; 3 \; 6 \; 9$。
DFS 序有一个很重要的性质,以节点 $u$ 为根的子树中所有的节点在 DFS 序中是连续的一段,并以 $u$ 开头。比如上图中,以 $2$ 为根的子树就有 $1 \; [{\color{red}{2}} \; {\color{red}{4}} \; {\color{red}{7}} \; {\color{red}{8}} \; {\color{red}{5}}] \; 3 \; 6 \; 9$。以 $6$ 为根的子树就有 $1 \; 2 \; 4 \; 7 \; 8 \; 5 \; 3 \; [{\color{red}{6}} \; {\color{red}{9}}]$。以 $5$ 为根的子树就有 $1 \; 2 \; 4 \; 7 \; 8 \; [{\color{red}{5}}] \; 3 \; 6 \; 9$,以此类推。
如何求得以 $u$ 为根的棵子树在 DFS 序中对应的连续一段序列的左端点和右端点呢?定义 $\text{tin}_u$ 表示第一次访问到节点 $u$ 的时间,对应序列的左端点。定义 $\text{out}_u$ 表示访问完以 $u$ 为根的子树的时间,对应序列的右端点。可以通过以下代码求出每个节点的 $\text{tin}$ 和 $\text{tout}$。
void dfs(int u, int pre) {
tin[u] = ++sz;
for (int i = head[u]; i != -1; i = ne[i]) {
if (e[i] != pre) {
dfs(e[i], u);
}
}
tout[u] = sz;
}
上图中每个节点的 $\text{tin}$ 和 $\text{tout}$ 就是:
节点编号 | $1$ | $2$ | $4$ | $7$ | $8$ | $5$ | $3$ | $6$ | $9$ |
$\text{tin}$ | $1$ | $2$ | $3$ | $4$ | $5$ | $6$ | $7$ | $8$ | $9$ |
$\text{tout}$ | $9$ | $6$ | $5$ | $4$ | $5$ | $6$ | $9$ | $9$ | $9$ |
DFS 序可以将树形结构转换成序列的形式,这使得在子树上进行的修改和查询操作可以转换为区间修改和区间查询,而区间的操作可以结合线段树或树状数组来优化。比如有以下简单的应用。
如果修改操作为给某个子树中的节点都加上一个值,查询操作为求某个节点的值。那么就可以求出 DFS 序,然后用树状数组维护一个差分数组,就变成了区间修改,单点查询。当给以 $u$ 为根的子树中的节点都加上 $c$,那么在差分数组中有 $d_{\text{tin}_u} \gets d_{\text{tin}_u} + c$,$d_{\text{tout}_u + 1} \gets d_{\text{tout}_u + 1} - c$。查询某个节点的值则对差分数组求前缀和即可。
如果修改操作为给某条链上的节点都加上一个值,查询操作为求某个节点的值(点差分)。这题不可以用一般的树上差分来实现,因为查询操作不一定在最后执行,但做法几乎还是一样的,只不过变成了维护关于 DFS 序的差分数组。假设链的两个端点为 $u$ 和 $v$,根据 $\mathrm{lca}(u,v)$ 分成两条链,然后有$$\displaylines{\begin{cases} d_{\text{tin}_u} \gets d_{\text{tin}_u} + c \\ d_{\text{tin}_{\mathrm{lca}(u,v)}} \gets d_{\text{tin}_{\mathrm{lca}(u,v)}} - c \\ d_{\text{tin}_v} \gets d_{\text{tin}_v} + c \\ d_{\text{tin}_{fa \left[ {\mathrm{lca}(u,v)} \right]}} \gets d_{\text{tin}_{fa \left[ {\mathrm{lca}(u,v)} \right]}} - c \end{cases}}$$
可以发现与一般树上差分不同的地方就在于把节点编号 $u$ 换成了 $\text{tin}_u$。单独考虑一条链的情况(某个点是另外一个点的祖先这种特殊情况),假设 $y$ 是 $x$ 的祖先,当对 $\text{tin}_x$ 加上 $c$ 后,那么所有含节点 $x$ 的子树的和都会加上 $c$,即对所有 $x$ 的祖先的子树都有贡献。但我们只想让从 $x$ 向上到 $y$ 这条路径上的节点的子树有贡献,为此还需要对 $\text{tin}_{fa[y]}$ 减去 $c$。参考上图 假设 $x=8, \, y=4$ 的情况:
查询某点 $u$ 的值只需求其子树的和即 $d_{\text{tin}_u} \sim d_{\text{tout}_u}$ 的区间和即可。这样就把原问题通过 DFS 序变成了单点修改区间查询的问题,可以用树状数组来维护。
其他更多关于 DFS 序的应用可以参考博文。
参考资料
DFS 序入门:https://www.luogu.com.cn/blog/p6174/dfs-xu-ru-men
dfs序和欧拉序:https://www.cnblogs.com/stxy-ferryman/p/7741970.html
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17842407.html