【动态规划】【贪心】 [POI2011] DYN-Dynamite
这俩东西是怎么结合到一起的?
题目描述
给一棵树,树上有一些关键节点,要求你选 \(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\) 的情况:
-
\(f_x = mid\) ,这时如果再拖延就没有办法覆盖到这个距离为 \(mid\) 的点了,所以 \(f_x = -inf,g_x = 0,cnt++\) ,在 \(x\) 处设关键点。
-
\(f_x + g_x \leq mid\) ,这时可以用子树中的 \(g_x\) 直接覆盖 \(f_x\) ,所以 \(f_x = -inf\) 。
-
\(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;
}