题解:P7165 [COCI 2020/2021 #1] Papričice
jackzhangcn2013 · · 题解
题目大意
有一棵树,割两条边,使分成的三部分最大与最小部分之差最小。
解法
如果暴力枚举割的两条边,那时间复杂度最优也只能到
于是我们可以想到先固定一条边,再确定剩下的一条边。
由于已经分了一部分了,我们肯定希望剩下两部分的差尽量小,假设现在已经分了的部分大小为
时间复杂度为
实现
先预处理出所有节点的子树大小。
假设割的一条边为
可以发现令一条边割在
祖先节点
记录下所有
其他节点
与祖先节点同理,但由于其他节点的子树并不包含
最后统计答案即可。
代码
#include <bits/stdc++.h>
#define int long long
const int N = 2e5 + 5;
const int Mod = 1e9 + 7;
using namespace std;
int n;
vector<int> g[N];
int siz[N];
void pre(int u, int fa)
{
siz[u] = 1;
for (int v : g[u])
{
if (v != fa)
{
pre(v, u);
siz[u] += siz[v];
}
}
}
multiset<int> other, father;
int ans = 1e18 + 5;
inline void upd(int a, int b, int c)
{
ans = min(ans, max({a, b, c}) - min({a, b, c}));
}
void dfs(int u, int fa) // 第一刀剪在 u 和 fa 上
{
// 第二刀可能在父节点上
if (!father.empty())
{
auto it = father.lower_bound((n - siz[u]) / 2 + siz[u]); // 父节点子节点包含 u,加上补偿
if (it != father.end())
{
upd(siz[u], *it - siz[u], n - *it);
}
if (it != father.begin())
{
it--;
upd(siz[u], *it - siz[u], n - *it);
}
}
// 第二刀可能在其他子树上
if (!other.empty())
{
auto it = other.lower_bound((n - siz[u]) / 2);
if (it != other.end())
{
upd(siz[u], *it, n - *it - siz[u]);
}
if (it != other.begin())
{
it--;
upd(siz[u], *it, n - *it - siz[u]);
}
}
if (u > 1) // 1 没有父节点
{
father.insert(siz[u]);
}
for (int v : g[u])
{
if (v != fa)
{
dfs(v, u);
}
}
if (u > 1)
{
father.erase(father.find(siz[u]));
other.insert(siz[u]);
}
}
signed main()
{
cin >> n;
for (int i = 1; i < n; i++)
{
int x, y;
cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
siz[1] = 1;
pre(1, 0);
dfs(1, 0);
cout << ans;
return 0;
}