DYN / 消防局的设立 / Spread of Information / 将军令 / Parkovi 题解

前言

五倍经验:[POI2011] DYN-Dynamite[HNOI2003] 消防局的设立[ARC116E] Spread of Information将军令[COCI2021-2022#4] Parkovi

题意简述

给你一棵 \(n\) 个结点的树和点集 \(S\),你要选出 \(k\) 个关键点 \(T\),求 \(\min \max \limits _ {u \in S} \min \limits _ {v \in T} \operatorname{dis}(u, v)\)

\(1 \leq k \lt n \leq 3 \times 10^5\)

题目分析

最大值最小?一眼二分。考虑 check(x) 表示是否存在一种 \(T\),满足 \(\max \limits _ {u \in S} \min \limits _ {v \in T} \operatorname{dis}(u, v) \leq x\)

那么转变成了对于 \(u \in S\),要求距离其 \(x\) 内存在一个 \(v \in T\)。我们求出满足条件的 \(\min |T|\),与 \(k\) 作比较,若 \(\min |T| \leq k\) 说明存在这样的 \(T\)

考虑贪心。从叶子向上考虑,当前点能不放就不放入 \(T\),只有当不放入时会导致字树内某一个 \(v \in S\) 距离它超过 \(x\),则必须要放进 \(T\)。正确性是显然的,读者自证不难。

我们考虑记 \(f_u\) 表示 \(u\) 子树里最远不合法的 \(v \in S\) 距离 \(u\) 的距离。向上转移是简单的,\(f_u = \max f_v + 1\)。但是,不要忘记了,有可能子树间可会互相影响。所以我们再记 \(g_u\) 表示 \(u\) 子树里最近的 \(v \in T\) 距离 \(u\) 的距离,转移和 \(f\) 类似。

接下来考虑 \(u\) 是否必须放入 \(T\),如果 \(f_u = x\),说明必须选择 \(u\),即 \(T \gets T \cup \{u\}\)\(f_u \gets -\infty\)\(g_u \gets 0\),同时让答案加一;

否则如果 \(f_u + g_u \leq x\),说明子树间可以互相解决,所以 \(f_u \gets 0\)

不要忘记,如果根的 \(f \neq -\infty\),需要额外一个点。

时间复杂度:\(\Theta(n \log n)\)

代码

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

const int N = 300010;

int n, m;
bool have[N];
vector<int> edge[N];

int tot = 0, f[N], g[N];

void dfs(int now, int fa, int x) {
	f[now] = have[now] ? 0 : -0x3f3f3f3f;
	g[now] = 0x3f3f3f3f;
	for (int to: edge[now]) if (to != fa) {
		dfs(to, now, x);
		f[now] = max(f[now], f[to] + 1);
		g[now] = min(g[now], g[to] + 1);
	}
	if (f[now] == x) {
		++tot;
		f[now] = -0x3f3f3f3f;
		g[now] = 0;
	} else if (f[now] + g[now] <= x) {
		f[now] = -0x3f3f3f3f;
	}
}

bool check(int x) {
	tot = 0, dfs(1, 0, x);
	if (f[1] >= 0) ++tot;
	return tot <= m;
}

signed main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, x; i <= n; ++i) scanf("%d", &x), have[i] = x;
	for (int i = 1, u, v; i <= n - 1; ++i) {
		scanf("%d%d", &u, &v);
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	int l = 0, r = n, mid, ans = 0;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf("%d", ans);
	return 0;
}
posted @ 2024-10-27 16:49  XuYueming  阅读(11)  评论(0编辑  收藏  举报