P1395 会议

P1395 会议

题目描述

有一个村庄居住着 $n$ 个村民,有 $n-1$ 条路径使得这 $n$ 个村民的家联通,每条路径的长度都为 $1$。现在村长希望在某个村民家中召开一场会议,村长希望所有村民到会议地点的距离之和最小,那么村长应该要把会议地点设置在哪个村民的家中,并且这个距离总和最小是多少?若有多个节点都满足条件,则选择节点编号最小的那个点。

输入格式

第一行,一个数 $n$,表示有 $n$ 个村民。

接下来 $n-1$ 行,每行两个数字 $a$ 和 $b$,表示村民 $a$ 的家和村民 $b$ 的家之间存在一条路径。

输出格式

一行输出两个数字 $x$ 和 $y$。

$x$ 表示村长将会在哪个村民家中举办会议。

$y$ 表示距离之和的最小值。

输入输出样例 #1

输入 #1

1
2
3
4
4
1 2
2 3
3 4

输出 #1

1
2 4

说明/提示

数据范围

对于 $70\%$ 数据 $n \le 10^3$。

对于 $100\%$ 数据 $n \le 5 \times 10^4$。

思路分析 + 代码实现

树的重心

树的重心的有关证明在这里均省略

定义:

无根树 $T$ 中的结点 $v$ 是它的重心,当且仅当以下任意一条成立:

  1. 在树中删去结点 $v$ 后,得到的图 $T\setminus{v}$ 中每个连通分量的大小均不超过原树结点数的一半。

  2. 在所有删去某个结点后得到的最大连通分量大小中,删去结点 $v$ 时所得到的值最小。

  3. 树中所有结点到某个结点的距离和中,到结点 $v$ 的距离和最小。
    引自OI Wiki

由定义 3 可知,本题需要求树的编号较小的重心(树的重心如果不唯一,则恰有两个)

求法:

根据重心的等价定义,有 DFS 统计子树大小换根 DP 统计深度和 两种方法可以在 $O(n)$ 时间内求出树的所有重心。这里用第一种。

  • 用 $sz[i]$ 储存以节点 $i$ 为根的子树大小(所有子树上节点数 + 该节点)。

  • 用 $w[i]$ 储存节点 $i$ 的重量,即 $i$ 的所有子树大小(不包括该节点)的最大值。

  • 对每个结点,记录它的所有子结点对应子树的大小,并更新 $w[i]$ 。再利用总结点数减去当前子树大小得到“向上”的子树的大小,再更新 $w[i]$ 。

  • 如果 $w[i] \leq n / 2$ ,则节点 $i$ 符合重心的定义,更新重心。

本题代码如下(树的重心求法见函数 get_center )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 5e4 + 5;
int w[MAXN], dep[MAXN], sz[MAXN], n;
vector<int> G[MAXN];
void get_center(int u, int f, int &tar) {
sz[u] = 1;
for(int v : G[u]) {
if (v == f) continue;
get_center(v, u, tar);
sz[u] += sz[v];
w[u] = max(w[u], sz[v]);
}
w[u] = max(w[u], n - sz[u]);
if (w[u] <= n / 2)
tar = min(tar, u);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int u, v, i = 1; i < n; i++) {
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
int center = MAXN, ans = 0;
get_center(1, 0, center);
queue<int> Q;
Q.push(center);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for(int v : G[u]) {
if (dep[v] || v == center) continue;
dep[v] = dep[u] + 1;
ans += dep[v];
Q.push(v);
}
}
cout << center << ' ' << ans;
return 0;
}

其中 BFS 求距离和(深度和)也可以用 DFS ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int dfs(int u, int f) {
int acc = dep[u];
for(int v : G[u]) {
if (v == f) continue;
dep[v] = dep[u] + 1;
acc += dfs(v, u);
}
return acc;
}
int main() {
// 省略读入和求重心
int ans = dfs(center, 0);
cout << center << ' ' << ans;
return 0;
}