【洛谷P3523】DYN-Dynamite
前言
开学了,几天没写题了。今天政治课出来随机跳了一道题写写。。。
题目
题目链接:https://www.luogu.com.cn/problem/P3523
给一棵树,树上有一些关键节点,要求你选\(m\)个点,使得关键节点到这些点中距离的最小值的最大值最小,求这个值。
思路
\(\operatorname{Update:}\) 标记点即关键点。
最大值最小,套路性二分转变为判定性问题。
假设现在二分到最大距离为\(mid\),我们只需要判断若使每一个关键节点到选择的点的距离不超过\(mid\),最小选取点的个数是否不超过\(m\)即可。
容易想到,如果在\(x\)的子树下我们已经选择了若干个点,设\(x\)子树内没有被覆盖的标记点到\(x\)的距离为\(dis\),如果\(dis<mid\),那么显然没有必要选择\(x\)点,因为在\(x\)的父亲上选显然更优。
所以我们得到了一个结论:选取一个点,当且仅当在它的子树内没有被覆盖的标记点到该点的距离等于\(mid\)。
那么这样就可以贪心选点了。设\(f[x][1]\)表示\(x\)的子树内最远没有被覆盖的标记点到\(x\)的距离,\(f[x][2]\)表示\(x\)的子树内最近的被选择的点到\(x\)的距离。
显然有
那么在\(x\)的各个儿子的子树互相做贡献,如果\(f[x][1]+f[x][2]\leq mid\),那么\(x\)的其中一棵子树的选择的点可以将其他所有子树的未覆盖标记点,那么就将\(f[x][1]\)赋值为\(-\infty\)。
如果\(f[x][1]=mid\),那么必须选择\(x\)点,此时\(f[x][1]=-\infty,f[x][2]=0\)。并且选择的节点数加一。
最后记得特判根节点。
时间复杂度\(O(n\log n)\)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=300010,Inf=1e9;
int n,m,tot,l,r,mid,cnt,head[N],f[N][3];
bool flag[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void dfs(int x,int fa)
{
f[x][1]=-Inf; f[x][2]=Inf;
if (flag[x]) f[x][1]=0;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
dfs(v,x);
f[x][1]=max(f[x][1],f[v][1]+1);
f[x][2]=min(f[x][2],f[v][2]+1);
}
}
if (f[x][1]+f[x][2]<=mid) f[x][1]=-Inf;
if (f[x][1]>=mid) cnt++,f[x][2]=0,f[x][1]=-Inf;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&flag[i]);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
l=0; r=n;
while (l<=r)
{
mid=(l+r)>>1; cnt=0;
dfs(1,0);
if (f[1][1]>=0) cnt++;
if (cnt<=m) r=mid-1;
else l=mid+1;
}
printf("%d\n",r+1);
return 0;
}