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
", ans);
}
int main() {return work(), 0;}
原文地址:https://www.cnblogs.com/Sunny-r/p/12651879.html