c o d e f o r c e s 936 ( D i v 3 ) C . T r e e C u t t i n g \Huge{codeforces~936(Div3)~C.Tree Cutting} codeforces 936(Div3) C.TreeCutting
题意
给出一颗有 n n n个节点的树,然后给出 n − 1 n-1 n−1条边的情况。然后给出一个 k k k,要求删掉树中的 k k k条边,要求删掉边后的节点最少的子树的节点数最多。 ( 1 ≤ k < n ≤ 1 0 5 ) (1\le k < n \le 10^5) (1≤k<n≤105)
思路
根据题意,题目要求节点最少的子树的节点数最多,那么当为结果 r e s res res时, r e s − 1 , r e s − 2 … res-1,res-2… res−1,res−2…也可以通过删边得到。所以结果满足单调性,考虑使用二分。
由于是树形结构,我们可以假设在每个点保存其后代节点的个数,表示以当前点作为根节点时子树大小。
那么如果结果为res时,我们首先应该将节点个数大于等于res的最小值的子树剪掉。
但是这个思路实现过程中,如果通过最后剪了 k k k次之后最小子树是否为 r e s res res来判断,比较难以实现。
因此,考虑当只有子树符合要求时剪去,否则不剪,若最后剪的次数大于等于 k k k,则当前 r e s res res满足要求,二分中间值应缩小。
标程
int n, k;
vector<int> a[N];
bool check(int mid) {
int t = 0;
auto dfs = [&](auto self, int x, int y) -> int {
int sum = 1;
for(auto i : a[x]) {
if(i == y) continue;
sum += self(self, i, x);//从叶子节点向上返回节点个数
}
if(sum >= mid && x != y) t ++, sum = 0;//如果个数符合就将其剪去,可以用向上返回0实现
return sum;
};
int x = dfs(dfs, 1, 1);
if(t > k || (t == k && x >= mid)) return true;
else return false;
}
void Solved() {
cin >> n >> k;
for(int i = 1; i <= n; i ++ ) a[i].clear();//init
for(int i = 1; i < n; i ++ ) {
int x, y; cin >> x >> y;
a[x].push_back(y); a[y].push_back(x);
}
int l = 1, r = n / (k + 1) + 1, mid;
while(r - l > 1) {
mid = l + r >> 1;
if(check(mid)) l = mid;
else r = mid;
}
cout << l << endl;
}