图论·并查集·题解【Cow at Large G·atlarge】


洛谷题目链接
WOJ题目链接

题目

洛谷[USACO18JAN] P4186 Cow at Large G
WOJ#3562 atlarge

题目描述

最后,Bessie被迫去了一个远方的农场。这个农场包含N个谷仓(2 <= N <= 105)和N-1条连接两个谷仓的双向隧道,所以每两个谷仓之间都有唯一的路径。每个只与一条隧道相连的谷仓都是农场的出口。当早晨来临的时候,Bessie将在某个谷仓露面,然后试图到达一个出口。
但当Bessie露面的时候,她的位置就会暴露。一些农民在那时将从不同的出口谷仓出发尝试抓住Bessie。农民和Bessie的移动速度相同(在每个单位时间内,每个农民都可以从一个谷仓移动到相邻的一个谷仓,同时Bessie也可以这么做)。农民们和Bessie总是知道对方在哪里。如果在任意时刻,某个农民和Bessie处于同一个谷仓或在穿过同一个隧道,农民就可以抓住Bessie。反过来,如果Bessie在农民们抓住她之前到达一个出口谷仓,Bessie就可以逃走。
Bessie不确定她成功的机会,这取决于被雇佣的农民的数量。给定Bessie露面的谷仓K,帮助Bessie确定为了抓住她所需要的农民的最小数量。假定农民们会自己选择最佳的方案来安排他们出发的出口谷仓。

输入

输入的第一行包含N和K。接下来的N – 1行,每行有两个整数(在1~N范围内)描述连接两个谷仓的一条隧道。

输出

输出为了确保抓住Bessie所需的农民的最小数量。

样例

  • 输入样例
    7 1
    1 2
    1 3
    3 4
    3 5
    4 6
    5 7
  • 输出样例
    3

题意

给定一棵有根树。贝茜从根节点出发,出口是每个叶子节点,可以在每个出口放一个农民,每1个单位时间内,贝茜和农民都可以移动到相邻的一个点,如果某一时刻农民与贝茜相遇了(在边上或点上均算),则贝茜将被抓住。求为了保证抓住贝茜,最少需要几个农民。

思路

首先明确一点:每个农民,每次都移动到自己的父亲节点,显然是最优的。
然后考虑最坏的情况:这棵树只有两层(如下图)。
因为懒得画图所以就拿mermaid将就一下啦凑合着看吧。

root
1
2
3
4
...

此时为了满足条件,我们需要在每一个叶子节点,都放一个农民。
然后考虑优化:当节点 i i i到根节点的距离,大于它到以 i i i为根的叶子结点 j j j的距离时,以 i i i为根的子树,就只需要安放一个 j j j节点就够了(如下图)。

root
1
2
...
i
j

所以我们预处理出了 i i i的子树上距 i i i最近的叶子节点 j j j i i i的最短距离,存储在 l a s t [ i ] last[i] last[i]中,将节点 i i i的深度存储在 d e p [ i ] dep[i] dep[i]中。然后遍历,如果 l a s t [ i ] &lt; = d e p [ i ] last[i]&lt;=dep[i] last[i]<=dep[i],则 i i i的子树均可以用这一个守卫守住,否则要去下面用更多的守卫( a n s + + ans++ ans++)。算一下答案即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
int n,m,root,cnt=2,ans=0;
int first[maxn],dep[maxn],last[maxn],fa[maxn];
struct edge {int u,v,nxt;};
edge e[maxn<<1];
inline int read()
{
	int x=0,f=1;char c;
	for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
	if(c=='-') {f=-1;c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*f;
}
inline void add(int u,int v)
{
	e[cnt].u=u;e[cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt++;
}
void dfs(int x)
{
	int son=0;
	for(int i=first[x];i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x]) continue;
		son++;
		fa[v]=x;dep[v]=dep[x]+1;
		dfs(v);
		last[x]=min(last[x],last[v]+1);
	}
	if(!son)last[x]=0;
}
void solve(int x)
{
	if(last[x]<=dep[x]) {ans++;return;}
	for(int i=first[x];i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x]) continue;
		solve(v);
	}
}
int main()
{
	memset(last,0x3f,sizeof(last));
	n=read(),root=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		add(u,v);add(v,u);
	}
	dfs(root);
	solve(root);
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值