【洛谷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\)的距离。
显然有

\[f[x][1]=\max_{v\in x's\ son}(f[v][1]+1) \]

\[f[x][2]=\min_{v\in x's\ son}(f[v][2]+1) \]

那么在\(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;
}
posted @ 2020-02-19 16:57  stoorz  阅读(140)  评论(0编辑  收藏  举报