树上最小点覆盖: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 
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;
}

你可能感兴趣的:(c++算法,#,dp学习记录,算法,深度优先)