[2019 ICPC 徐州 M ] Kill the tree
https://nanti.jisuanke.com/t/42552
题意:
定义\(c(w)\)为树中所有点到\(w\)点的距离和。对于一棵以节点\(1\)为根节点的无向树,我们要求出其每棵子树中的\(c(w)\)最小的点,若有多个,则从小到大输出。
思路:
即求每棵子树的重心。肯定不能对于每棵子树都重新开始求,那我们考虑一棵树的重心和它的子树重心之间的关系,发现树的重心一定在其子树重心之中深度最深的那个点到根节点的连线上。我们在求重心的时候,要让所有点到它的距离和最小,会求将这个点删除后,剩下所有子树的最大节点数,让这个最大节点数最小的点即是重心,这个重心一定会往最大的子树去靠。那么现在有多个最大的子树,如果其中一棵子树的重心深度最深,那么这个重心对应的子树大小一定是最大的,所以重心会往这棵子树移。但是别的子树可能会让它往上移,所以我们只要从这个重心往上移就可以了(不会具体的证明Orz,只会这样土想)。同时,由于重心的性质,我们将重心删除后,其最大子树的大小一定不会超过树的大小的一半,利用这个性质能比较方便地找出重心。
注意这道题求最大子树大小的时候,不需要考虑\(n - sz[u]\)的大小。因为求子树的时候,不需要考虑上面的节点。然后注意一棵树的重心可能有很多个,多个重心一定是相邻的,注意及时break,减少时间复杂度。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
struct Edge{
int nxt, v;
Edge () {}
Edge (int nxt, int v) : nxt(nxt), v(v) {}
}edge[N << 1];
int head[N], tot;
int dp[N], sz[N], pre[N], ans[N][3];
int n;
void init() {
memset(head, -1, sizeof(head));
tot = 0;
}
void addedge(int u, int v) {
edge[tot] = Edge(head[u], v);
head[u] = tot++;
edge[tot] = Edge(head[v], u);
head[v] = tot++;
}
void dfs(int u, int fa) {
sz[u] = 1;
for (int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v;
if (v != fa) {
dfs(v, u);
sz[u] += sz[v];
pre[v] = u;
dp[u] = max(dp[u], sz[v]);
}
}
for (int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v;
if (v == fa) continue;
int f = 0;
int now = ans[v][1];
while (now != u) {
int maxx = max(dp[now], sz[u] - sz[now]);
if (maxx * 2 <= sz[u]) {
if (!ans[u][0]) ans[u][ ++ans[u][0] ] = now;
else if (ans[u][ ans[u][0] ] != now) {
ans[u][ ++ans[u][0] ] = now;
}
}
if (f) break;
if (ans[u][0]) f = 1;
now = pre[now];
}
}
if (dp[u] * 2 <= sz[u]) ans[u][ ++ans[u][0] ] = u;
}
void solve() {
init();
scanf("%d", &n);
for (int i = 1, u, v; i < n; ++i) {
scanf("%d %d", &u, &v);
addedge(u, v);
}
dfs(1, 0);
for (int i = 1; i <= n; ++i) {
if (ans[i][0] == 2) {
printf("%d %d\n", min(ans[i][1], ans[i][2]), max(ans[i][1], ans[i][2]));
} else {
printf("%d\n", ans[i][1]);
}
}
}
int main() {
int t = 1;
solve();
return 0;
}
/*
10
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
*/

浙公网安备 33010602011771号