给出一个 $n$ 个节点的树,$1$ 号点为根。现要将其中一些点染成黑色,使得每个叶子节点(不包括根节点)到根节点路径上的黑点数相同。求最多能够染多少个黑点。
题解
贪心
显然有结论:选择的黑点尽量靠近叶子节点。
并且显然每个点到根节点路径上的黑点数是:深度最小的叶子节点到根节点路径上的点数。
那么首先预处理出每个点子树内深度最小的叶子节点的深度,然后进行贪心过程:显然如果一个点染黑,那么它到其子树内深度最小的叶子节点路径上的所有点都要染黑。我们维护每个点到根节点的路径上染了多少黑点,就能根据已经染黑的节点数和它到其字数内深度最小的叶子节点路径上的点数即可判断出当前点是否能够选择。
时间复杂度 $O(n)$
#include <cstdio>
#include <algorithm>
#define N 100010
using namespace std;
int head[N] , to[N << 1] , next[N << 1] , cnt , deep[N] , md[N] , now[N] , ans;
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
int i;
if(x != 1 && !next[head[x]]) md[x] = deep[x];
else md[x] = 1 << 30;
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa)
deep[to[i]] = deep[x] + 1 , dfs(to[i] , x) , md[x] = min(md[x] , md[to[i]]);
}
void solve(int x , int fa)
{
int i;
now[x] = now[fa];
if(now[fa] + md[x] - deep[x] == md[1]) now[x] ++ , ans ++ ;
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa)
solve(to[i] , x);
}
int main()
{
int n , i , x , y;
scanf("%d" , &n);
for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x);
dfs(1 , 0);
solve(1 , 0);
printf("%d\n" , ans);
return 0;
}