传送门
题意: 给定一颗树, 若在i点安放了一个消防站, 那么在树上距离它不超过2的所有点都可以被照顾到, 那么要让整棵树都要照顾到最少需要安放多少个消防站,
思路: 贪心, 就是找最深没被覆盖到的点, 并在它的祖父处设一个消防站. 考虑到这个点的所有子孙后代都已经被覆盖了, 因此这时覆盖祖父能盖到更多额外的点, 并保证结果不会更差.
我们在输入时只要预处理出深度(边输入边处理)并排序, 碰到已覆盖就跳过, 未覆盖就在祖父处设消防站, ans++。
问题在于怎样才能判断这个点覆盖到了没有. 对于儿子或孙子覆盖他, 可以在在儿子处设站时就标记它;而对于父亲和祖父覆盖他, 可以用儿子对父亲的映射f来解决; 问题在于兄弟. 其实, 可以用d数组维护“离i最近的消防站到i的距离”,当o[父亲]==1时, 就能确定它是否被覆盖
具体细节请看代码实现:
AC Code
int fa[maxn], a[maxn], d[maxn], dep[maxn];
bool cmp(int x, int y) {
return dep[x] > dep[y];
}
void solve() {
int n; scanf("%d", &n);
a[1] = dep[1] = 1; d[1] = d[0] = n;
for (int i = 2 ; i <= n ; i ++) {
int x; scanf("%d", &x);
d[i] = n; fa[i] = x; a[i] = i;
dep[i] = dep[x] + 1;
}
sort(a+1, a+1+n, cmp); int ans = 0;
for (int i = 1 ; i <= n ; i ++) {
int u = a[i], f1 = fa[u], f2 = fa[fa[u]];
d[u] = min(d[u], min(d[f1]+1, d[f2]+2));
if (d[u] > 2) {
d[f2] = 0; ++ ans;
d[fa[f2]] = min(d[fa[f2]], 1);
d[fa[fa[f2]]] = min(d[fa[fa[f2]]], 2);
}
}
printf("%d\n", ans);
}
并且, 这种方法的普适性很强, 可以解决半径为k的最小覆盖问题. 而且不用存图. 只需要把维护“父亲和爷爷”改成维护“上位k位祖先”即可, 复杂度O(N*K), 常数也很小.