ARC 116 E 题解
题目大意很简单,同时显然看出单调性,直接二分答案,问题变为给定最远覆盖距离,求最少覆盖点。
这是一个经典的贪心问题。事实上,他很简单。
我们提取并形式化题目:
- 给定一棵树和值 \(k\),选取 \(s\) 个点涂黑。
- 对于每一个点,如果离他最近的颜色为黑的点,与他的距离,小于等于 \(k\),我们认为这个点“被覆盖”。
- 黑点显然被覆盖。
- 求最少被涂黑的点。
他的贪心体现在,假设一个链很长,这个链的叶子节点和根的距离是最远的,那么这个叶子节点的至少第 \(k\) 级祖先需要被染色,同时,给恰好第 \(k\) 级祖先涂黑显然是最优的,因为这可以让这个祖先的子树全部被覆盖,好过任何其他方案。
所以枚举最深点到最浅点,如果枚举到没有被覆盖的点,就把他的 \(k\) 级祖先涂黑。这是贪心。
但事实上,我们需要更巧妙的方法来维护每一个点是否被覆盖、是否需要被涂黑。
我们假设 \(f_x\) 表示 \(x\) 到他的子树中最远的未被覆盖的点的距离,\(g_x\) 表示 \(x\) 到他的子树中最近的被涂黑的点。特殊的,如果 \(f_x = -inf\),表示 \(x\) 的子树被全部覆盖,\(g_x = inf\),表示 \(x\) 的子树都没被涂黑。
我们默认 \(f_x = -inf\),如果他没有被覆盖,我们会改变他。
分讨。
- 如果 \(g_x > k\),表示 \(x\) 没有被覆盖,那么 \(f_x = \max(f_x, 0)\)。
- 如果 \(f_x + g_x <= k\),表示离 \(x\) 最近的黑点可以覆盖到离 \(x\) 最远的未被覆盖的点,这时 \(f_x = -inf\)。
- 如果 \(f_x = k\),那么 \(x\) 就必须被染色,所以令 \(f_x = -inf, g_x = 0\),同时将答案加 \(1\)。
最后判断离 \(1\) 最远的未被覆盖的点与他的距离,如果大于等于 \(0\),则把答案加 \(1\)。
代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
auto mread = [](){int x;scanf("%lld", &x);return x;};
const int N = 2e5 + 5;
int n = mread(), k = mread(), f[N], g[N], sum = 0;
vector<int> v[N];
void dfs(int x, int fa, int mid){
f[x] = LONG_LONG_MIN, g[x] = 0x3f3f3f3f3f3f3f3f;
for(int y : v[x]){
if(y == fa){
continue;
}
dfs(y, x, mid);
f[x] = max(f[x], f[y] + 1);
g[x] = min(g[x], g[y] + 1);
}
if(g[x] > mid){
f[x] = max(0ll, f[x]);
}
if(f[x] + g[x] <= mid){
f[x] = LONG_LONG_MIN;
}
if(f[x] == mid){
f[x] = LONG_LONG_MIN;
g[x] = 0;
sum ++;
}
return;
}
int check(int mid){
sum = 0;
dfs(1, 1, mid);
sum += (f[1] >= 0ll);
return sum;
}
signed main(){
for(int i = 1, x, y; i < n; i ++){
x = mread(), y = mread();
v[x].push_back(y);
v[y].push_back(x);
}
int l = 0, r = n;
while(l < r){
int mid = l + r >> 1;
if(check(mid) <= k){
r = mid;
}
else{
l = mid + 1;
}
}
printf("%lld\n", l);
return 0;
}

浙公网安备 33010602011771号