JZOJ 【2020.11.30提高組模擬】剪辣椒(chilli)
阿新 • • 發佈:2020-11-30
題目大意
給出一棵 \(n\) 個節點的樹,刪去其中兩條邊
使得分出的三個子樹大小中最大與最小的差最小
分析
先一邊 \(dfs\) 預處理出以 \(1\) 為根每個點的 \(size\)
然後按 \(dfs\) 的順序列舉一個點,表示刪去這個點返回父親的邊
記這個點為 \(x\)
分類討論
第一種情況是刪去當前點到根的一條邊,記這條邊下面的點為 \(y\)
這種情況會使子樹 \(y\) 包含子樹 \(x\)
第二種情況是刪去不包含子樹 \(x\) 的已經遍歷過的邊
這種情況須令外處理
於是我們算答案是要做兩遍 \(dfs\)
具體細節看程式碼
\(Code\)
#include<cstdio> #include<iostream> #include<set> using namespace std; const int N = 2e5 + 5; int n, h[N], siz[N], ans; set<int> s; set<int>::iterator it; struct edge{int to, nxt;}e[N << 1]; void add(int x, int y) { static int tot = 0; e[++tot] = edge{y, h[x]}, h[x] = tot; } void dfs(int x, int fa) { siz[x] = 1; for(register int i = h[x], v; i; i = e[i].nxt) { v = e[i].to; if (v == fa) continue; dfs(v, x), siz[x] += siz[v]; } } int getc(int x, int y, int z) { int a = max(max(x, y), z), b = min(min(x, y), z); return a - b; } void calc(int x) { int d = (n - siz[x]) >> 1; it = s.lower_bound(d); if (it != s.end()) ans = min(ans, getc(siz[x], *it, n - siz[x] - *it)); if (it != s.begin()) --it, ans = min(ans, getc(siz[x], *it, n - siz[x] - *it)); } void dfs1(int x, int fa) { calc(x); s.insert(n - siz[x]); for(register int i = h[x], v; i; i = e[i].nxt) { v = e[i].to; if (v == fa) continue; dfs1(v, x); } s.erase(n - siz[x]); } void dfs2(int x, int fa) { for(register int i = h[x], v; i; i = e[i].nxt) { v = e[i].to; if (v == fa) continue; calc(v), dfs2(v, x); } s.insert(siz[x]); } int main() { freopen("chilli.in", "r", stdin); freopen("chilli.out", "w", stdout); scanf("%d", &n); for(register int i = 1, x, y; i < n; i++) scanf("%d%d", &x, &y), add(x, y), add(y, x); ans = 0x3f3f3f3f, dfs(1, 0), dfs1(1, 0), s.clear(), dfs2(1, 0); printf("%d", ans); }