前言
这道题是一个树上二分的题,其实很有思维量的
思路
首先这是一道让选择m个节点,每一个点到关键节点的距离的最小值中的最大值最小化的一道题目
首先第一个最小值就是每一个选定节点到最近的关键节点的距离,其实不用太在意,之后就是最大值最小化,这显然是二分答案的一道题,所以考虑二分一个mid,看看可不可以用mid的距离去覆盖全部的关键节点,之后就是考虑这个check
check
首先我们可以用覆盖的思想去理解这道题,就是每一个选定节点可以覆盖全部的关键节点就是一个true,那么我们就用两个数组
f[x] --> 表示以x为root的子树中距离最大的没有覆盖的关键节点的dis
g[x] – > 表示以x为root的子树中距离最近的选定节点的距离
那么转移就是 f[x]=max(f[x],f[y]+1) g[x]=min(g[x],g[y]+1)
这个比较显然,注意最开始f[x]=-inf g[x]=inf
那么之后就是考虑对于节点的选定
假如 1.f[x]+g[x]<=mid 也就说明这个子树的全部关键节点都可以被覆盖
2. g[x]>mid && b[x] == 1 也就是说明这个点是关键节点,并且没人可以管,所以就交给父亲处理 所以f[x]=max(f[x],0)
3. f[x] = mid 也就是说明这个子树内有一个关键节点没有办法被任何人覆盖,只有这个点,所以tot++
最后就是二分了
注意
二分的左边界可以为0,因为我可以距离为0
还有就是dfs结束后我们要特判一下根的f,假如有节点没有被覆盖,那么就要tot++,也就是把根给加上
code
#include<bits/stdc++.h>
using namespace std;
#define max(a,b) ((a)>(b)? a : b)
#define min(a,b) ((a)>(b)? b : a)
#define ll long long
const int maxn=300005;
const ll inf=1e9;
int n,m;
int head[2*maxn],to[2*maxn],nex[2*maxn],num=0;
void add(int x,int y){
to[++num]=y;
nex[num]=head[x];
head[x]=num;
}
ll f[maxn];//以i为根的子树,没有被覆盖的关键节点的最远距离
ll g[maxn];//以i为根的子树,被选定的节点中的最小距离
int b[maxn],tot=0;
void dfs(int x,int fa,int mid){
f[x]=-inf,g[x]=inf;
for(int i=head[x];i;i=nex[i])
{
int y=to[i];
if(y==fa) continue;
dfs(y,x,mid);
f[x]=max(f[x],f[y]+1);
g[x]=min(g[x],g[y]+1);//这就是对于定义的转移
}
if(g[x]+f[x]<=mid) f[x]=-inf;//不需要覆盖了, 就赋值为负无穷
if(g[x]>mid && b[x] == 1) f[x]=max(f[x],0);//这个点是关键节点,并且最近的无法覆盖,那么就让父亲覆盖
if(f[x]==mid) tot++,f[x]=-inf,g[x]=0;//把自己作为关键节点
return;
}
bool check(int x){
tot=0;
dfs(1,0,x);
if(f[1]>=0) tot++;//就是说根节点还有需要覆盖的
return tot<=m;
}
void get_ans(){
int l=0,r=n,ans;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d\n",ans);
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
get_ans();
return 0;
}