给定 (u,v),如何 O(1) 求 lca(u,v) 的孩子 u',v',且分别为 u,v 的祖先或本身

问题描述

一棵树,\(q\) 次询问给定 \(u,v\),保证 \(u,v\) 不为祖孙关系。设 \(p=\operatorname{lca}(u,v)\),求 \(p\) 的一个孩子 \(u'\)\(u'\)\(u\) 的祖先或 \(u\) 本身,类似需要求 \(v'\)

朴素解法

倍增可以做到 \(\mathcal{O}(\log n)\),但太慢了。

进阶解法

\(\mathcal{O}(1)\) 求 LCA 结合长链剖分 \(\mathcal{O}(1)\) 求 kth-father。真的有人会写这个吗?

优化解法

不妨令 \(\mathrm{idx}(u)<\mathrm{idx}(v)\),考虑到 DFS 序求 \(p=\operatorname{lca}(u,v)\) 最后一步跳父亲前,如果 ST 表对于相同深度的点,选取 \(\operatorname{idx}\) 大的,就是求出了 \(v'\),得到了 \(p\) 后,通过求 \(\operatorname{lca}(u,p)\) 最后一步不要跳父亲,就能得到 \(u'\)

时间复杂度 \(\mathcal{O}(1)\)

代码实现

优化解法
#include <cstdio>
#include <iostream>
#include <vector>
#include <tuple>
#include <cassert>
using namespace std;

const int N = 5e5 + 10;
const int lgN = __lg(N) + 1;

int n, q, rt;
vector<int> e[N];

int st[lgN][N], idx[N], dpt[N], fa[N], timer, R[N];

void dfs(int u) {
    st[0][idx[u] = ++timer] = u;
    for (int v : e[u]) {
        if (v == fa[u]) continue;
        fa[v] = u;
        dpt[v] = dpt[u] + 1;
        dfs(v);
    }
    R[u] = timer;
}
inline int Min(int u, int v) {
    return dpt[u] < dpt[v] ? u : v;
}
inline tuple<int, int, int> lca(int u, int v) {
    if (u == v) return { 0, 0, u };
    int U, V, p, iu = idx[u], iv = idx[v];
    bool F = false;
    if (iu > iv) swap(iu, iv), swap(u, v), F = true;
    int k = __lg(iv - iu);
    V = Min(st[k][iu + 1], st[k][iv - (1 << k) + 1]);
    p = fa[V];
    if (p == u) U = 0;
    else {
        int ip = idx[p];
        k = __lg(iu - ip++);
        U = Min(st[k][ip], st[k][iu - (1 << k) + 1]);
    }
    if (F) swap(U, V);
    return { U, V, p };
}

signed main() {
    scanf("%d%d%d", &n, &q, &rt);
    for (int i = 1, u, v; i < n; ++i) {
        scanf("%d%d", &u, &v);
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(rt);
    for (int k = 1; k < lgN; ++k)
        for (int i = 1; i + (1 << k) - 1 <= n; ++i)
            st[k][i] = Min(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
    for (int u, v; q--; ) {
        scanf("%d%d", &u, &v);
        auto [U, V, p] = lca(u, v);
        printf("%d\n", p);
        fprintf(stderr, "u' = %d, v' = %d\n", U, V);
        assert(!U || (idx[U] <= idx[u] && idx[u] <= R[U]));
        assert(!V || (idx[V] <= idx[v] && idx[v] <= R[V]));
    }
    return 0;
}
朴素倍增
#include <cstdio>
#include <iostream>
#include <vector>
#include <tuple>
#include <cassert>
using namespace std;

const int N = 5e5 + 10;
const int lgN = __lg(N) + 1;

int n, q, rt;
vector<int> e[N];

int idx[N], dpt[N], fa[lgN][N], timer, R[N];

void dfs(int u) {
    idx[u] = ++timer;
    for (int v : e[u]) {
        if (v == fa[0][u]) continue;
        fa[0][v] = u;
        dpt[v] = dpt[u] + 1;
        dfs(v);
    }
    R[u] = timer;
}

inline tuple<int, int, int> lca(int u, int v) {
    if (u == v) return { 0, 0, u };
    bool F = false;
    int p, U, V;
    if (dpt[u] < dpt[v]) swap(u, v), F = true;
    for (int i = lgN - 1; ~i; --i)
        if (fa[i][u] && dpt[fa[i][u]] > dpt[v])
            u = fa[i][u];
    if (fa[0][u] == v) {
        U = u, V = 0, p = v;
    } else {
        if (dpt[u] != dpt[v])
            u = fa[0][u];
        for (int i = lgN - 1; ~i; --i)
            if (fa[i][u] != fa[i][v])
                u = fa[i][u], v = fa[i][v];
        U = u, V = v, p = fa[0][u];
    }
    if (F) swap(U, V);
    return { U, V, p };
}

signed main() {
    scanf("%d%d%d", &n, &q, &rt);
    for (int i = 1, u, v; i < n; ++i) {
        scanf("%d%d", &u, &v);
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(rt);
    for (int k = 1; k < lgN; ++k)
        for (int i = 1; i <= n; ++i)
            fa[k][i] = fa[k - 1][fa[k - 1][i]];
    for (int u, v; q--; ) {
        scanf("%d%d", &u, &v);
        auto [U, V, p] = lca(u, v);
        printf("%d\n", p);
        fprintf(stderr, "u' = %d, v' = %d\n", U, V);
        assert(!U || (idx[U] <= idx[u] && idx[u] <= R[U]));
        assert(!V || (idx[V] <= idx[v] && idx[v] <= R[V]));
    }
    return 0;
}

算法比较

不妨用这题来比较。朴素倍增会超时,优化解法总耗时 \(4.30\mathrm{s}\)。使用链式前向星存图,注释 fprintf 后,朴素倍增总耗时 \(2.73\mathrm{s}\),优化解法总耗时 \(1.36\mathrm{s}\)

posted @ 2025-06-08 10:26  XuYueming  阅读(62)  评论(0)    收藏  举报