給定 N 節點樹(N ≤ 100,000)。刪除某節點後,樹分裂為若干子樹(分支)。若**最大分支節點數 ≤ N/2**,稱該刪除「叼爆」(即該節點為**樹的重心**)。求所有重心節點編號(遞增輸出),無則輸出 `NONE`。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 110000;
struct Edge { int to, next; } e[MAXN << 1];
int head[MAXN], ecnt;
int dp[MAXN], ans[MAXN], acnt;
int N;
void addEdge(int u, int v) {
e[ecnt] = {v, head[u]};
head[u] = ecnt++;
}
void dfs1(int u, int fa) {
dp[u] = 1;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dfs1(v, u);
dp[u] += dp[v];
}
}
void dfs2(int u, int fa) {
int maxPart = 0;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dfs2(v, u);
maxPart = max(maxPart, dp[v]); // 子樹分支
}
maxPart = max(maxPart, N - dp[u]); // 父側分支
if (maxPart <= N / 2) ans[acnt++] = u;
}
int main() {
while (scanf("%d", &N) == 1) {
memset(head, -1, sizeof(int) * (N + 1));
ecnt = acnt = 0;
for (int i = 1; i < N; i++) {
int a, b;
scanf("%d %d", &a, &b);
addEdge(a, b);
addEdge(b, a);
}
dfs1(1, 0);
dfs2(1, 0);
sort(ans, ans + acnt);
if (acnt == 0) puts("NONE");
else for (int i = 0; i < acnt; i++) printf("%d\n", ans[i]);
}
return 0;
}
10
1 2
2 3
3 4
4 5
6 7
7 8
8 9
9 10
3 8
輸出:`3` 和 `8`(兩個重心)。
| 環節 | 關鍵 |
|---|---|
| 鄰接表 | 靜態陣列 `Edge e[MAXN*2]` + `head[]`,避免 vector 開銷 |
| 遞歸深度 | N=100,000 可能棧溢位,建議改用**非遞歸棧**或編譯器擴大棧(`-Wl,--stack=268435456`) |
| 重心性質 | 樹最多有 2 個重心,且相鄰 |
| 輸出順序 | 題目要求遞增,故 `sort` |
O(N) 兩次 DFS + O(N log N) 排序(重心極少,實際 O(N))。O(N) 邊表、dp、ans。// 方法 1:手動棧模擬 dfs1
void dfs1_iter(int root) {
static int stk[MAXN], top, fa[MAXN], state[MAXN];
top = 0;
stk[top++] = root;
fa[root] = 0;
while (top) {
int u = stk[top - 1];
if (state[u] == 0) { // 首次訪問
state[u] = 1;
dp[u] = 1;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (v == fa[u]) continue;
fa[v] = u;
stk[top++] = v;
}
} else { // 子樹已處理完,回溯
top--;
if (fa[u]) dp[fa[u]] += dp[u];
}
}
}
同樣是樹形 DP 找重心,但輸出**重心編號**與**最大分支大小的最小值**。代碼幾乎相同,僅 `dfs2` 中記錄 `minMaxPart` 對應節點。