题意补充
- 事实上,(A)每次能染的(k)个节点并不一定与(B)所在节点相邻,换言之,(A)可以提前染一些节点使得(B)以后走不到。
- 数据范围:(1 leq n leq 300000)
算法
二分答案 + 树形DP
思路
二分每次染的节点个数(k),再用树形DP检查;
设(f_u)为以(u)节点为根的子树(不含(u))全部染完除了(k)以外还需要的次数,那么:
[f_u = max(sum{f_v} ; +siz_u - k, 0)
]
其中,(siz_u)为(u)的儿子数量。最后当(f_1 = 0)时即为合法。
注意要从儿子往父亲推,这样才能避免不知道先处理哪一个儿子的情况。
为什么贪心是错的:
考虑这种情况:有一个节点有大量儿子,而它的兄弟节点们只有一个儿子,显然,贪心是不好处理的。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 10;
int n,head[maxn << 1],num;
struct Edge{
int then,to;
}e[maxn << 1];
void add(int u, int v){e[++num] = (Edge){head[u], v}; head[u] = num;}
int f[maxn];
void DFS(int u, int fa, int ch){
int cnt = 0;
for(int i = head[u]; i; i = e[i].then){
int v = e[i].to;
if(v != fa){
cnt ++;
DFS(v, u, ch);
f[u] += f[v];
}
}
f[u] = max(0, f[u] + cnt - ch);
}
int check(int p){
memset(f,0,sizeof(f));
DFS(1, 1, p);
return (f[1] <= 0);
}
int main(){
scanf("%d", &n);
for(int i = 1; i < n; ++ i){
int u,v; scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
int l = 0, r = maxn;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
printf("%d
", r + 1);
return 0;
}
福利数据
7
1 2
1 3
2 5
2 6
7 2
4 1
ans:3