题面
题意
给出一棵树,树上有一些关键点,现在你最多选择m个点,使所有关键点到这些你选择的点的最小距离中的最大值最小.
做法
一开始想到的是树形dp,但因为数据过大,且处理麻烦.
正确做法是先二分答案,再用贪心的思想来检验,但记录仍有麻烦
记录时每个点记录两个值,一个是离他最远的并且不在当前选择的点的覆盖范围内的点与他之间的距离,一个是离它最近的已选择的点距他的距离.
转移时,尽量不选择,除非此时第一个值等于当前距离,而若两个值之和小于等于当前距离,则可以用当前已选择点覆盖此点.
最后记得判断根节点是否需要选择即可
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 300100
#define P pair<int,int>
#define mp make_P
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
int n,m,first[N],bb,an,l,r,d;
bool gg[N];
struct Bn
{
int to,next;
}bn[N*2];
inline void add(int u,int v)
{
bb++;
bn[bb].to=v;
bn[bb].next=first[u];
first[u]=bb;
}
P dfs(int now,int last)
{
int p,q;
P tmp,res;
res.fi=-INF;
res.se=INF;
for(p=first[now];p!=-1;p=bn[p].next)
{
if(bn[p].to==last) continue;
tmp=dfs(bn[p].to,now);
res.fi=max(res.fi,tmp.fi+1);
res.se=min(res.se,tmp.se+1);
}
if(gg[now])
res.fi=max(res.fi,0);
if(res.fi+res.se<=d) res.fi=-INF;
if(res.fi==d)
an++,res.se=0,res.fi=-INF;
return res;
}
int main()
{
memset(first,-1,sizeof(first));
int i,j,p,q;
P tmp;
cin>>n>>m;
for(i=1;i<=n;i++)
{
scanf("%d",&gg[i]);
}
for(i=1;i<n;i++)
{
scanf("%d%d",&p,&q);
add(p,q);
add(q,p);
}
l=0,r=n;
for(;l+1<r;)
{
an=0;
d=(l+r)>>1;
tmp=dfs(1,-1);
an+=(tmp.fi+tmp.se>d);
an<=m?r=d:l=d;
}
if(l==r)
cout<<l;
else
{
an=0;
d=l;
tmp=dfs(1,-1);
an+=(tmp.fi+tmp.se>d);
an<=m?cout<<l:cout<<r;
}
}