📜  连续树(1)

📅  最后修改于: 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 序的顺序重排,得到连续树如下。

连续树示例2

RMQ 问题

以区间最小值(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;
}
总结

连续树能够快速解决基于区间的问题,常用于处理各种数据结构的维护。值得一提的是,连续树的构造方法十分简单快捷,只需要进行一次深度优先搜索即可。