树上最小点覆盖:P3523 [POI2011] DYN-Dynamite

传送门

前题提要:最近碰到了一种求树上最小点覆盖的题目,感觉有点典的,故写博客记录一下.

树上最小点覆盖:

即对于树上设定一些需要覆盖的点,然后让你选一些树上管辖(能覆盖)的点,并且你知道每一个管辖点能管辖的距离,问你最小需要几个点才能覆盖所有需要覆盖的点.

一般想法:

考虑对于一个需要管辖的点 v v v,这个点能被管辖的情况有两种,一种是 v v v的子树中的点,一种是 v v v的子树点外的点(也就是 v v v的父辈和兄弟).

贪心的考虑,显然我们如果想选一个子树外的点去管辖 v v v,那么这个点应该尽量的越远越好.因为距离越远的话,能管辖的范围就越大(无论是对于子树来说还是子树外来说).但是事实上,我们是存在很多的 v i vi vi的,所以我们上述尽量远的点是相对于最远的需要覆盖的点来说的.此处考虑使用树形dp来维护出最远需要覆盖的点到目前点的距离.在下文用 f [ ] f[] f[]表示.
但是上述的解法并没有考虑子树内的点的情况.因为对于一个点我们设置它为管辖的点,他不仅对于子树内的点存在贡献,还对子树外的点(也就是他的父辈或者兄弟)存在贡献.但是显然的对于子树外的点来说产生贡献的必然是子树内最近的管辖点.所以此处我们考虑使用树形dp来维护出最近的管辖点距当前点的距离.在下文中 g [ ] g[] g[]表示.

那么我们可以从下往上去dfs,因为枚举顺序是自下而上,根据上述的贪心,我们应该尽量的将管辖的点设置在深度较小的位置.
初始化 f [ u ] = − i n f , g [ u ] = i n f f[u]=-inf,g[u]=inf f[u]=inf,g[u]=inf代表没有最远/最近的点.
首先考虑 f , g f,g f,g数组一般的递推式,显然是 f [ u ] = f [ v ] + 1 , g [ u ] = g [ v ] + 1 f[u]=f[v]+1,g[u]=g[v]+1 f[u]=f[v]+1,g[u]=g[v]+1
考虑一个点 u u u是否需要管辖,如果该点需要管辖,那么此时我们应该 f [ u ] = m a x ( f [ u ] , 0 ) f[u]=max(f[u],0) f[u]=max(f[u],0).
然后考虑如果 g [ u ] + f [ u ] < = 能管辖距离 g[u]+f[u]<=能管辖距离 g[u]+f[u]<=能管辖距离,那么此时意味着我们的u子树中的所有点都是能被之前选出来的管辖点所覆盖的,那么此时我们应该将 f [ u ] = − i n f f[u]=-inf f[u]=inf,代表u子树没有需要覆盖的点.
然后如果存在 f [ u ] = = 能管辖距离 f[u]==能管辖距离 f[u]==能管辖距离,因为经过了上述的两种情况的判断,如果还存在这种情况,意味着此时我们的u节点必须得设置管辖点了,因为既没法被子树内的点覆盖,又没办法继续贪下去了.所以此时 f [ u ] = − i n f , g [ u ] = 0 f[u]=-inf,g[u]=0 f[u]=inf,g[u]=0,并且需要的点数+1.

最终需要特判一下根节点的情况,因为对于根节点来说,也是没办法继续贪下去了.


下面提供P3523的代码(解题思路就是在上述想法上套个二分即可):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int state[maxn];vector<int>edge[maxn];
int f[maxn],g[maxn];
//子树内未覆盖的最远距离
//子树内覆盖点的最近距离
int cnt=0;
void dfs(int u,int per_u,int mid) {
	f[u]=-int_INF;g[u]=int_INF;
	for(auto v:edge[u]) {
		if(v==per_u) continue;
		dfs(v,u,mid);
		f[u]=max(f[v]+1,f[u]);
		g[u]=min(g[v]+1,g[u]);
	}
	if(state[u]) f[u]=max(f[u],0);
	if(g[u]+f[u]<=mid) f[u]=-int_INF;
	if(f[u]==mid) ++cnt,f[u]=-int_INF,g[u]=0;
}
int n,m;
int check(int mid) {
	cnt=0;
	dfs(1,0,mid);
	if(f[1]>=0) cnt++;
	return cnt<=m;
}
int main() {
	n=read();m=read();
	for(int i=1;i<=n;i++) {
		state[i]=read();
	}
	for(int i=1;i<=n-1;i++) {
		int u=read();int v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	int l=0,r=n;int ans;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(check(mid)) {
			ans=mid;
			r=mid-1;
		}
		else {
			l=mid+1;
		}
	}
	cout<<ans<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值