题意简述:

有一个 个节点的树,点编号为
次询问。每次询问给出一个子集 ,令
其中 表示点 与点 的距离。


做法简述

因为
所以
暴力求的话,复杂度是平方的。
我们需要优化。
从等式的右边的意思出发。
我们需要求的就是某个点 ,它到点集中所有点的最大距离最小。
因为要求最小,所以这个 点必须也要在点集 中。
所以我们可以换个角度考虑。既然 都在点集 中了,我们可以直接求 的直径即可,所以 点的深度要尽可能地大。
求法不再赘述(前人之述备矣),代码里会有一些注释。
时间复杂度 ,可以通过。


代码

#include <bits/stdc++.h>
using namespace std;
const int maxN = 300005;
struct Edge {
    int to, nxt;
} edge[maxN << 1];
int fa[maxN][20], dep[maxN], hd[maxN], n, tot;
void rd(int&), dfs(int, int), add(int, int);
int LCA(int, int), dis(int, int);
int main() {
    rd(n);
    for (int i = 1; i < n; i++) {
        int u, v;
        rd(u);
        rd(v);
        add(u, v);
        add(v, u);
    }
    dfs(1, 1); // 先 dfs 预处理 fa 和 dep
    int q;
    rd(q);
    while (q--) {
        int S;
        rd(S);
        vector<int> ver(S + 1); // 点集
        int id = 0, max_dep = 1, ans = 0;
        for (int i = 1; i <= S; i++) {
            rd(ver[i]);
            if (dep[ver[i]] > max_dep) {
                max_dep = dep[ver[i]];
                id = ver[i];
            }
        // 找出点集中深度最大的点
        }
        // id 即是题解中的 v 点
        for (int i = 1; i <= S; i++) {
            if (id == ver[i]) {
                continue;
            }
            int d = dis(id, ver[i]);
            ans = max(ans, d);
        // 计算树的直径
        }
        printf("%d\n", (ans + 1) / 2);
    }
}
void dfs(int u, int d) {
    dep[u] = d;
    for (int i = hd[u]; i; i = edge[i].nxt) {
        int o = edge[i].to;
        if (dep[o]) {
            continue;
        }
        fa[o][0] = u;
        for (int j = 1; fa[o][j - 1]; j++) {
            fa[o][j] = fa[fa[o][j - 1]][j - 1];
        }
        dfs(o, d + 1);
    }
}
int LCA(int u, int v) { // 经典倍增 LCA 求法
    if (dep[u] < dep[v]) {
        swap(u, v);
    }
    // 先强制使 u 点的深度比 v 点大
    for (int i = 19; i >= 0; i--) {
        if (dep[u] - (1 << i) >= dep[v]) {
            u = fa[u][i];
        }
    }
    // 用倍增思想让 u 点往上跳
    if (u == v) {
        return u;
    }
    for (int i = 19; i >= 0; i--) {
        if (fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    // 然后 u 和 v 一起往上跳
    return fa[u][0];
}
int dis(int u, int v) { // 计算 u 点与 v 点之间的距离
    return dep[u] + dep[v] - 2 * dep[LCA(u, v)];
}
void add(int x, int y) {
    edge[++tot].to = y;
    edge[tot].nxt = hd[x];
    hd[x] = tot;
}
void rd(int& x) {
    x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
}