JZOJ 【2020.11.30提高组模拟】剪辣椒(chilli)
题目大意
给出一棵 \(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);
}