前言
四倍经验:[POI2011] DYN-Dynamite;[HNOI2003] 消防局的设立;[ARC116E] Spread of Information;将军令。
题意简述
给你一棵 \(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\)。正确性是显然的,想要证明可以把最优答案的点向下移动来证明,读者自证不难。
我们考虑记 \(f_u\) 表示 \(u\) 子树里最远不合法的 \(v \in S\) 距离 \(u\) 的距离。向上转移、统计答案是简单的。但是,不要忘记了,有可能子树间可会互相影响。所以我们再记 \(g_u\) 表示 \(u\) 子树里最近的 \(v \in T\) 距离 \(u\) 的距离,转移同样简单。
如果 \(f_u = x\),说明必须选择 \(u\),即 \(T \gets T \cup \{u\}\),\(f_u = -\infty\),\(g_u = 0\);
否则如果 \(f_u + g_u \leq x\),说明子树间可以互相解决,所以 \(f_u \gets 0\)。
不要忘记,如果根的 \(f \neq -\infty\),需要额外一个点。
代码
#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 (const auto& 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;
}
标签:Information,limits,min,int,题解,tot,leq,now,将军令
From: https://www.cnblogs.com/XuYueming/p/18508482