📅  最后修改于: 2023-12-03 14:57:58.115000             🧑  作者: Mango
连续树是一种特殊的树结构,其节点之间的关系满足连续性。具体地,连续树上的每个节点都有一个编号,编号连续地从 $1$ 到 $n$(n 为节点数)。按照这个编号对树进行遍历时,相邻的两个节点编号之差为 $1$。如下图所示,就是一棵包含 $7$ 个节点的连续树,其中节点 $i$ 的编号为 $i$。
连续树很适合用来维护各种基于区间的信息,比如 RMQ、线段树等。其主要优点是计算方便快捷。在连续树上执行一些操作,通常只需要关注节点的编号即可,不需要记录其他的信息。
构造连续树,最常用的方法是将一颗普通的树转化为连续树。具体的构造方法为:对每个节点进行 DFS 遍历,并存储下该节点的 DFS 序编号。如对于下图所示的树,按 DFS 序遍历的结果是 $1,2,4,5,3,6$。
接着,我们将节点按照 DFS 序的顺序重排,得到连续树如下。
以区间最小值(RMQ)问题为例,介绍如何使用连续树来解决这个问题。先将普通树转化为连续树,然后建立一颗线段树来维护节点的值。
int a[maxn]; // 存储节点的权值
int dfn[maxn]; // 存储 DFS 序编号
void dfs(int u, int fa) {
static int idx = 0;
dfn[u] = ++idx;
for (int v : G[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
void build(int o, int l, int r) {
if (l == r) {
tree[o] = a[l];
return;
}
int mid = (l + r) / 2;
build(o * 2, l, mid);
build(o * 2 + 1, mid + 1, r);
tree[o] = min(tree[o * 2], tree[o * 2 + 1]);
}
int query(int o, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) { // ql 和 qr 为查询区间
return tree[o];
}
int mid = (l + r) / 2, ans = INF;
if (ql <= mid) ans = min(ans, query(o * 2, l, mid, ql, qr));
if (mid < qr) ans = min(ans, query(o * 2 + 1, mid + 1, r, ql, qr));
return ans;
}
具体来说,建立线段树时,我们将每个节点的权值存储在 DFN 序的相应位置,即节点 $u$ 的编号为 $dfn[u]$ 的位置上。
查询区间最小值时,我们需要先将普通树上的区间转化为连续树上的区间,然后再在线段树上执行查询操作。具体而言,假设查询区间为 $[l,r]$,那么连续树上的查询区间就是 $[dfn[l],dfn[r]]$。
int lca(int u, int v) {
int l = idom(u, v);
return dep[u] < dep[v] ? (dep[l] < dep[u] ? l : u) : (dep[l] < dep[v] ? l : v);
}
int idom(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
int main() {
dfs(1, 0);
build(1, 1, n);
int q; scanf("%d", &q);
while (q--) {
int u, v; scanf("%d%d", &u, &v);
int p = lca(u, v);
int ans = min(query(1, 1, n, dfn[u], dfn[p]), query(1, 1, n, dfn[v], dfn[p]));
printf("%d\n", ans);
}
return 0;
}
连续树能够快速解决基于区间的问题,常用于处理各种数据结构的维护。值得一提的是,连续树的构造方法十分简单快捷,只需要进行一次深度优先搜索即可。