P3523 [POI2011]DYN-Dynamite
题目描述
给一棵树,树上有一些关键节点,要求你选\(m\)个点,使得关键节点到这些点中距离的最大值最小,求这个值。
题解
最大值最小,考虑二分答案,二分这个最小值。
那么怎么判断呢,我们只需要记录一下至少要选多少个点,只要点数\(\leq m\)则合法。
考虑树形\(dp\),设\(f1[x]\)表示\(x\)的子树中距离它最远的关键点到它的距离,设\(f2[x]\)表示\(x\)的子树中距离它最近的选出的点到它的距离,\(is[x]\)表示\(x\)这个节点是否为关键节点。
首先从儿子向父亲转移时显然\(f1[x] = max(f1[x], f1[y] + 1), f2[x] = min(f2[x], f2[y] + 1)\),其中\(y\)是\(x\)的儿子,初始化\(f1[x] = - inf, f2[x] = inf\)。
若\(is[x]\),则\(f1[x] = max(0, f1[x])\);
若\(f1[x] + f2[x] <= mid\),则\(f1[x] = -inf\)。
若\(f1[x] == mid\),则\(f1[x] = - inf, f2[x] = 0, cnt ++\)。(表示这个点被选了)。
注:最后判断一下根节点是否还需要选上。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 3e5 + 5;
const int inf = 0x3f3f3f3f;
int n, m, is[N], ans, head[N], tot, cnt, f1[N], f2[N];
struct node{int to, nex;}a[N << 1];
inline int read()
{
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
void add(int x, int y) {a[++ tot].to = y; a[tot].nex = head[x]; head[x] = tot;}
void dfs(int x, int fa, int mid)
{
f1[x] = - inf; f2[x] = inf;
for(int i = head[x]; i; i = a[i].nex)
{
int y = a[i].to;
if(y == fa) continue;
dfs(y, x, mid);
f1[x] = max(f1[x], f1[y] + 1);
f2[x] = min(f2[x], f2[y] + 1);
}
if(is[x]) f1[x] = max(0, f1[x]);
if(f1[x] + f2[x] <= mid) f1[x] = - inf;
if(f1[x] == mid) f1[x] = - inf, f2[x] = 0, cnt ++;
}
int check(int mid)
{
cnt = 0; dfs(1, 0, mid);
if(f1[1] >= 0) cnt ++;
return cnt <= m;
}
void work()
{
n = read(); m = read();
for(int i = 1; i <= n; i ++) is[i] = read();
for(int i = 1, x, y; i < n; i ++) {x = read(); y = read(); add(x, y); add(y, x);}
int l = 0, r = n;
while(l <= r)
{
int mid = (l + r) >> 1;
if(check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
}
int main() {return work(), 0;}