【动态规划】【贪心】 [POI2011] DYN-Dynamite

发布时间 2023-11-28 08:44:11作者: The_Last_Candy

这俩东西是怎么结合到一起的?

题目描述

给一棵树,树上有一些关键节点,要求你选 \(m\) 个点,第 \(i\) 个关键节点到这些点中每个点距离的最小值记为 \(dis_i\),记这全部 \(dis\) 的最大值为 \(K\),现在要使 \(K\) 最小,求这个 \(K\)

\(1 \leq n,m \leq 3 \times 10^5\)

算法描述

让最大值最小,二分答案判断可行性。假设现在 \(dis\) 的最大值为 \(mid\),相当于要所有的关键点到最近的选择点距离都小于等于 \(mid\) ,我们发现一棵子树内的关键点,要么已经在子树中解决,要么需要一条链从外面进来覆盖它。所以我们考虑记 \(f_x\)\(x\) 子树中没有被覆盖关键点到 \(x\) 的最大距离。

但是,存在一种从子树中心选的点覆盖这个 \(f_x\) 的情况,所以我们再考虑设 \(g_x\)\(x\) 子树中选择的点到 \(x\) 的最小距离。

\[f_x = \max_{son} f_{son} + 1 \]

\[g_x = \min_{son} g_{son} + 1 \]

我们考虑几种需要修改 \(f,g\) 的情况:

  1. \(f_x = mid\) ,这时如果再拖延就没有办法覆盖到这个距离为 \(mid\) 的点了,所以 \(f_x = -inf,g_x = 0,cnt++\) ,在 \(x\) 处设关键点。

  2. \(f_x + g_x \leq mid\) ,这时可以用子树中的 \(g_x\) 直接覆盖 \(f_x\) ,所以 \(f_x = -inf\)

  3. \(g_x > mid,d_x = 1\) ,这个点是关键点,且无法被子树中的点覆盖,所以需要记录这个关键点为 “未覆盖的点”,\(f_x = \max(f_x,0)\)

然后二分答案时判断 \(cnt \leq m\) 即可。

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

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5,inf = 0x3f3f3f3f;
int f[N],g[N],n,m,cnt = 0,mid,d[N];
vector <int> G[N];
inline void dp(int x,int last)
{
	f[x] = -inf; g[x] = inf;
	for(auto to : G[x])
	{
		if(to == last) continue;
		dp(to,x);
		f[x] = max(f[x],f[to] + 1);
		g[x] = min(g[x],g[to] + 1);
	}
	if(f[x] + g[x] <= mid) f[x] = -inf;
	if(g[x] > mid && d[x] == 1) f[x] = max(f[x],0);
	if(f[x] == mid) ++cnt,f[x] = -inf,g[x] = 0;
}
inline bool check() 
{
	cnt = 0;
	dp(1,0);
	if(f[1] >= 0) cnt++;
	return cnt <= m;
}
int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i = 1;i <= n;i++) cin>>d[i];
	for(int i = 1,x,y;i <= n - 1;i++)
	{
		cin>>x>>y;
		G[x].push_back(y);
		G[y].push_back(x);
	}
	int l = 0,r = n;
	while(l < r)
	{
		mid = (l + r) >> 1;
		if(check()) r = mid;
		else l = mid + 1;
	}
	cout<<l;
	return 0;
}