注意以下几个点:
- 1.对于一个树,总节点数为n,边i-j断开时,j所在新树的节点数为x时,i所在新树的节点数为n-x
- 2.利用1的特性,实际尝试断一条边即可, 在DFS过程中补全所有子树的节点数,不需要尝试断每一条边
- 3.把边的方向时作为键时,在C++或Java中容易数据溢出或者超出题目要求的内存限制,还是用类似”i==>j”的形式表达边的方向
import java.util.*;
import java.io.*;
public class Main {
private static int n; // 一共的顶点数
private static Map<Integer, List<Integer>> adj = new HashMap<>(); // 记录子节点(可能有多个)
private static boolean[] visited;
private static Map<String, Integer> cntMap = new HashMap<>(); // 以A-->B作为键,值为以B作为根节点的树的节点总数
// DFS求当前的联通分量内点的个数,point是当前遍历到的点
public static int dfs(int point, int parent) {
int result = 1; // 至少当前节点算一个节点
String key = parent + "==>" + point;
if (cntMap.get(key) != null) return cntMap.get(key);
visited[point] = true;
for (int nextPoint : adj.get(point)) {
if (!visited[nextPoint]) {
// 当前节点加上子树的节点数
result += dfs(nextPoint, point);
}
}
cntMap.put(key, result);
cntMap.put(point + "==>" + parent, n - result);
return result;
}
public static void main(String[] args) throws IOException {
BufferedReader b = new BufferedReader(new InputStreamReader(System.in));
String s = b.readLine();
n = Integer.parseInt(s);
for (int i = 0; i < n - 1; i++) {
String[] edge = b.readLine().split(" ");
int edge0 = Integer.parseInt(edge[0]);
int edge1 = Integer.parseInt(edge[1]);
if (adj.get(edge0) == null) {
adj.put(edge0, new ArrayList<>());
}
adj.get(edge0).add(edge1);
if (adj.get(edge1) == null) {
adj.put(edge1, new ArrayList<>());
}
adj.get(edge1).add(edge0);
}
visited = new boolean[n + 1]; // 记录访问数组,用户BFS遍历过程中防止重复遍历
visited[1] = true; // 防止下面再重复判断了
// 因为是个树,1删除后,以1的邻接点开始遍历联通分量即可(1有几个邻接点就形成了几个联通分量)。而且树种不可能有孤立节点,所以不用担心adj.get(i)为null
// 遍历一次即可把所有可能的子树补全,即cntMap被全部考虑到
for (int j : adj.get(1)) dfs(j, 1);
int nodeCntMaxMin = Integer.MAX_VALUE;
// 每次尝试删除一个点,BFS求各个联通分量的最大顶点数,取所有删除点的情况的最小值,把本次删除的顶点输出
for (int i = 1; i <= n; i++) {
int nodeCntMax = 0;
for (int j : adj.get(i)) nodeCntMax = Math.max(nodeCntMax, cntMap.get(i + "==>" + j));
nodeCntMaxMin = Math.min(nodeCntMax, nodeCntMaxMin);
}
System.out.println(nodeCntMaxMin);
}
}