题意简述:
有一个 个节点的树,点编号为
。
有 次询问。每次询问给出一个子集
,令
。
其中 表示点
与点
的距离。
求 。
做法简述
因为 ,
所以 。
暴力求的话,复杂度是平方的。
我们需要优化。
从等式的右边的意思出发。
我们需要求的就是某个点 ,它到点集中所有点的最大距离最小。
因为要求最小,所以这个 点必须也要在点集
中。
所以我们可以换个角度考虑。既然 都在点集
中了,我们可以直接求
的直径即可,所以
点的深度要尽可能地大。
求法不再赘述(前人之述备矣),代码里会有一些注释。
时间复杂度 ,可以通过。
代码
#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();
}
}
京公网安备 11010502036488号