P2279 消防局的设立&&P3942 将军令(贪心)

P2279 消防局的设立

P3942 将军令

题目描述

给定一棵树,要求用若干个点将整棵树完全覆盖,其中每个点都可以覆盖与它的距离不超过 k k k的节点. 求最小的这样的点的数量.

题目分析

如果 k = 1 k=1 k=1 k = 2 k=2 k=2,那么显然可以使用树形dp来做;但是当这个 k k k很大的时候,动态规划就不适用了.

我们不妨回到题目:到底要求什么?

我们要求的是最小的覆盖整棵树的点数.

每个点都需要被覆盖.


贪心

这道题可以使用树形dp,这提示我们,无论把哪个点作为根,最后的结果都是一样的.

所以我们任意指定一个点为根节点,找出这棵树深度最大那个点,这个点一定需要被覆盖.

而且如果我们需要覆盖它,当然是要让覆盖它的点越浅越好,因为这个覆盖它的点越浅,能覆盖的点就越多.

那我们就找到当前最深的点,把覆盖它的点找到(和最深的点有k的距离),把这个覆盖它的点的所有能覆盖到的点打上标记,重复这个过程,直到整棵树全都被覆盖为止.

显然这样算出来的答案一定是最小的,不可能找到一种更小的方案. 时间复杂度为 O ( n k ) O(nk) O(nk) k k k为一个点能覆盖的范围.

程序实现

//P3942 将军令
#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
struct edge{
	int v,next;
}e[maxn<<1];
int head[maxn],tot;
void add(int u,int v){
	e[++tot].v =v;
	e[tot].next =head[u];
	head[u]=tot;
}
struct node{
	int dep,fa,pos;
	bool operator <(node x)const {
		return x.dep <dep;
	}
}s[maxn];
int n,len,type,ans;
vector<int >g[maxn<<1];
void init(int u,int pre){
	s[u].dep =s[pre].dep +1;
	g[s[u].dep ].push_back(u);
	for(int i=head[u];i;i=e[i].next ){
		int v=e[i].v ;
		if(v==pre)continue;
		init(v,u);
	}
	for(int i=g[s[u].dep +len].size()-1;i>=0;i--){
		int v=g[s[u].dep +len][i];
		s[v].fa =u;
		g[s[u].dep +len].pop_back();//只是对当前u的子树进行操作,不是所有的这个深度的点都在vector里,所以要pop
	}//找到每个点的覆盖它的点,vector维护每个深度的点有多少,以方便查找覆盖它的点
}
bool vis[maxn];
void dfs(int u,int dis,int pre){
	if(dis>len)return ;
	vis[u]=true;
	for(int i=head[u];i;i=e[i].next ){
		int v=e[i].v ;
		if(v==pre)continue;
		dfs(v,dis+1,u);
	}
}//对于每个没有覆盖到的深度最大的点,找到覆盖它的点并进行标记操作
int main(){
	scanf("%d%d%d",&n,&len,&type);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	for(int i=1;i<=n;i++) s[i].pos =i;
	init(1,1);//第一次dfs进行预处理,预处理每个点的深度和每个点的覆盖它的点
	//注意fa[1]=1,这样能够保证k>max_dep时的正确性.
	sort(s+1,s+n+1);//按深度排序
	for(int i=1;i<=n;i++){
		if(vis[s[i].pos ])continue;//如果这个点最深而且未访问
		dfs(s[i].fa ,0,s[i].fa );
		//第二次dfs找到覆盖它的点,并把这个点所能覆盖到的点全部打上标记
		ans++;
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值