P3523 [POI2011]DYN-Dynamite

前言

这道题是一个树上二分的题,其实很有思维量的

思路

首先这是一道让选择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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值