【2020.12.02提高组模拟】牧羊人(shepherd)

题目

题目描述

一棵树上有K只羊,树是一个没有环的简单连通图。这棵树有N个节点,用1到N表示。树的每个节点最多只能住一只羊。Soaring意识到,狼迟早会学会爬树。

为了保护羊,Soaring决定安排一些牧羊人住在在树中某些节点上,以确保每只羊至少被一个牧羊人保护。众所周知,每个牧羊人都会保护所有离他最近的羊,且只会保护它们。羊和牧羊人的距离等于两者之间简单路径的边数包含绵羊的节点和包含shepherd的节点之间唯一路径上的节点。此外,牧羊人可以跟羊住在一起,当然,这种情况下,牧羊人只保护这只羊。

计算最少需要放置多少个牧羊人,使得每只羊都至少被一个牧羊人保护。另外,输出一种放置方案。

数据范围

n ≤ 500000 n\leq500000 n500000

题解

题目大意

给出一棵树,一些点染了色 ,每个点可以覆盖所有离它最近的染色点,问最少需要多少个才能覆盖所有染色点,和具体放置方案

题目分析

考虑贪心

先预处理每个点到最近的染色点的距离 d i s i dis_i disi,这个可以两遍 d f s dfs dfs解决。然后每次找到一个深度最深的点,然后暴力往上跳,找到一个最浅的能覆盖它的点,然后输出,随即删点。删点的原则是 d i s y + 1 = d i s x dis_y+1=dis_x disy+1=disx,这样能保证 x x x一定可以覆盖到 y y y可以覆盖的点。由于每个点只会删一次,时间复杂度 O ( n ) O(n) O(n)

Code

#include<cstdio>
#include<algorithm>
#define inf 2147483600
#define N 500005
using namespace std;
struct node
{
	int head,next,to;
}a[N<<1];
struct node1
{
	int id,deep;
}p[N];
int n,m,x,y,ans,tot,sheep[N],deep[N],ans1[N],fa[N];
bool b[N],bj[N];
void add(int x,int y)
{
	a[++tot].to=y;
	a[tot].next=a[x].head;
	a[x].head=tot;
}
void getlen1(int now,int fa)
{
	sheep[now]=min(sheep[now],sheep[fa]+1);
	if (b[now]) sheep[now]=0;
	for (int i=a[now].head;i;i=a[i].next)
	{
		if (a[i].to==fa) continue;
		getlen1(a[i].to,now);
	}
}
void getlen2(int now,int fa)
{
	if (b[now]) sheep[now]=0;
	for (int i=a[now].head;i;i=a[i].next)
	{
		if (a[i].to==fa) continue;
		getlen2(a[i].to,now);
		sheep[now]=min(sheep[now],sheep[a[i].to]+1);
	}
}
void dfs(int now)
{
	deep[now]=deep[fa[now]]+1;
	for (int i=a[now].head;i;i=a[i].next)
	{
		if (a[i].to==fa[now]) continue;
		fa[a[i].to]=now;
		dfs(a[i].to);
	}
}
bool cmp(node1 x,node1 y) {return x.deep>y.deep;}
int up(int now)
{
	int len=0;
	while (len+1==sheep[fa[now]]&&!bj[fa[now]]) ++len,now=fa[now];
	return now;
}
void flag(int now,int fa)
{
	bj[now]=true;
	for (int i=a[now].head;i;i=a[i].next)
	{
		if (a[i].to==fa||bj[a[i].to]) continue;
		if (sheep[a[i].to]==sheep[now]-1) flag(a[i].to,now);
	}
}
int main()
{
	freopen("shepherd.in","r",stdin);
	freopen("shepherd.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;++i)
		scanf("%d%d",&x,&y),add(x,y),add(y,x);
	for (int i=1;i<=m;++i)
		scanf("%d",&p[i].id),b[p[i].id]=true;
	for (int i=0;i<=n;++i)
		sheep[i]=inf;
	getlen2(1,0);
	getlen1(1,0);
	sheep[0]=-1;
	dfs(1);
	for (int i=1;i<=m;++i)
		p[i].deep=deep[p[i].id];
	sort(p+1,p+m+1,cmp);
	for (int i=1;i<=m;++i)
	{
		if (!bj[p[i].id])
		{
			int num=up(p[i].id);
			flag(num,0);
			ans1[++ans]=num;
		}
	}
	printf("%d\n",ans);
	for (int i=1;i<=ans;++i)
		printf("%d ",ans1[i]);
	fclose(stdin);
	fclose(stdout);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值