F. Milk Factory

本文详细解析了GDUT2020寒假训练排位赛二F题,介绍了使用深度优先搜索算法解决特定图论问题的方法,包括构建邻接矩阵、遍历图结构及判断可达性的过程。

GDUT 2020寒假训练 排位赛二 F

原题链接

题目

原题截图
The milk business is booming! Farmer John’s milk processing factory consists of N processing stations, conveniently numbered 1…N (1≤N≤100), and N−1 walkways, each connecting some pair of stations. (Walkways are expensive, so Farmer John has elected to use the minimum number of walkways so that one can eventually reach any station starting from any other station). To try and improve efficiency, Farmer John installs a conveyor belt in each of its walkways. Unfortunately, he realizes too late that each conveyor belt only moves one way, so now travel along each walkway is only possible in a single direction! Now, it is no longer the case that one can travel from any station to any other station.

However, Farmer John thinks that all may not be lost, so long as there is at least one station i such that one can eventually travel to station i from every other station. Note that traveling to station i from another arbitrary station j may involve traveling through intermediate stations between i and j. Please help Farmer John figure out if such a station i exists.

Input
The first line contains an integer N, the number of processing stations. Each of the next N−1 lines contains two space-separated integers ai and bi with 1≤ai,bi≤N and ai≠bi. This indicates that there is a conveyor belt that moves from station ai to station bi, allowing travel only in the direction from ai to bi.

Output
If there exists a station i such that one can walk to station i from any other station, then output the minimal such i. Otherwise, output −1.

样例
input
3
1 2
3 2
output
2
题目大意

有n个点,和n-1对关系,表示可以从x走到y(单向),求是否存在一个编号最小的点i,使得从任意点出发都可以到达点i,若不存在则输出-1.

思路

之前的思路是并查集,对每一个关系进行合并操作,并将该集合的元素+1

fx=getfa(x);
fy=getfa(y);
if(fx!=fy)
{
	fa[fx]=fy;
	ans[fy]+=ans[fx]+1;
}

ans[i]表示以i为老大的集合的元素个数。
最后从1到n遍历ans数组,找到哪一个集合的元素个数为n-1就输出,都找不到就输出-1。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<memory.h>
using namespace std;
int fa[200];
int ans[200];
int getfa(int x)
{
	if(fa[x]==x)return x;
	else return fa[x]=getfa(fa[x]);	
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	memset(ans,0,sizeof ans);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		int fx,fy;
		fx=getfa(x);
		fy=getfa(y);
		if(fx!=fy)
		{
			fa[fx]=fy;
			ans[fy]+=ans[fx]+1;
		}
	}
	for(int i=1;i<=n;i++)
	{
		//cout<<ans[i]<<" ";
		if(ans[i]==n-1)
		{
			cout<<i<<endl;
			return 0;
		}
	}
	cout<<-1<<endl;
	return 0;
}

乍一看感觉莫得问题,但是提交就wa。
后来发现,这个并查集在路径压缩的时候将单向边的信息丢失了,也就是同属于一个集合但是并不能通过单向边到达。
比如
一种情况
这种情况,按照这个并查集的思路,1 2 3 4 同属于一个集合,而当构建1–>2,3–>2,3–>4这样的关系,输出的是4
当构建1–>2,3–>4,3–>2这样的关系,输出的是2
然而,这种情况不满足题意,也就是不存在这样的点,使得所有点都能到达该点,也就是应该输出-1.
那么通过这个例子发现,这样的并查集忽略了点与点之间的单向边的关系。所以决定采取深搜的办法。
深搜
构建邻接矩阵,对每一个点进行dfs,并在dfs的过程中记录经过点的个数,当经过点的个数与所有的的总数-1相等时输出这个点。

最后代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<memory.h>
using namespace std;
bool mp[105][105];
int cnt=0;
int n;
void dfs(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(mp[i][x]==1)
		{
			cnt++;
			dfs(i);
		}
	}
	return ;
}
int main()
{
	cin>>n;
	memset(mp,false,sizeof mp);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		mp[x][y]=1;
	}
	for(int i=1;i<=n;i++)
	{
		cnt=0;
		dfs(i);
		if(cnt==n-1)
		{
			cout<<i<<endl;
			return 0;
		}
	}
	cout<<-1<<endl;
	return 0;
}
<think>题目:P1700 [USACO19OPEN] Milk Factory B 题目描述:有N个站点(编号1~N)和N-1条单向通道,每条通道连接两个站点,且每条通道的牛奶只能单向流动。问是否存在一个站点,使得所有其他站点都可以通过一些通道到达该站点(即该站点是所有站点的汇点)。如果有多个这样的站点,输出编号最小的;如果没有,输出-1。 分析:题目要求找到一个汇点,即所有其他点都能到达的点。由于图中有N-1条单向边,整个图可以看作是一个有向图,且边数比点数少1,因此图可能是一个有向树(或森林)。但题目要求一个点能被所有其他点到达,所以这个点必须是唯一的根(因为边是单向的,且只有N-1条边,所以整个图应该是一个有根树,且根只有一个,否则无法满足所有点都能到达同一个点)。 然而,题目并没有保证图是连通的,但是边数只有N-1条,所以如果图是连通的,那么它是一棵树(有向树)。但是,题目要求的是所有点都能到达同一个点,那么在这个有向树中,这个点必须是根,并且所有边的方向都指向根?实际上,在有向树中,根是唯一没有出边的点,其他所有点都直接或间接指向根。 但是,题目中的边是单向的,而且并没有说方向是指向根还是远离根。所以我们需要重新考虑:题目要求所有点都能到达某个点S,那么S就是汇点。在图中,如果存在这样的点,那么从任意点出发,沿着边的方向都能走到S。因此,S的入度可以是任意的,但其他点必须至少有一条出边(除了S自己,S可以没有出边,但题目没有要求S的出边)。 实际上,我们只需要判断是否存在一个点,使得其他所有点都能到达它。我们可以考虑使用反向图:即把所有的边反向,然后在反向图中,如果存在一个点S,使得从S出发可以到达所有其他点,那么原图中所有点都能到达S。于是问题转化为:在反向图中,是否存在一个点,使得从该点出发可以遍历整个图?如果有多个,取编号最小的。 注意:题目中图可能有多个连通分量吗?题目没有明确说明,但边数只有N-1条,所以如果图是连通的,那么反向图也是连通的。但是,题目并没有保证图是连通的。所以我们需要考虑图不连通的情况。如果图不连通,那么肯定不存在一个点使得所有其他点都能到达它(因为存在两个连通分量,其中一个连通分量内的点无法到达另一个连通分量内的点)。因此,我们首先需要判断反向图是否连通。如果不连通,则输出-1;如果连通,则在反向图中,入度为0的点就是我们要找的点(因为反向图中,入度为0的点在原图中就是出度为0的点,即汇点,但这里我们是在反向图中找起点,所以反向图中入度为0的点就是原图中出度为0的点,而原图中出度为0的点最多只能有一个,因为如果有两个,那么这两个点之间无法互相到达,所以不可能同时被其他点到达。因此,在反向图中,入度为0的点只能有一个,否则就不存在满足条件的点)。 具体步骤: 1. 读取N,然后N-1条边,每条边是a->b(表示从a到b有一条单向边)。 2. 构建反向图:即对于每条边a->b,在反向图中添加一条边b->a。 3. 在反向图中,统计每个点的入度(即原图中每个点的出度)。 4. 在反向图中,入度为0的点(即原图中出度为0的点)应该恰好有一个。因为如果有多个入度为0的点,那么这些点之间无法互相到达(在反向图中,这些点都是起点,没有边指向它们,所以它们之间不可达),所以整个图就不连通。如果只有一个入度为0的点,那么从这个点出发进行BFS/DFS,看是否能遍历所有点(即判断反向图是否连通)。 5. 如果反向图连通且只有一个入度为0的点,那么这个点就是答案(在原图中,这个点就是汇点)。如果有多个入度为0的点,则输出-1(因为反向图不连通,所以原图也不存在一个汇点使得所有点都能到达它)。如果没有入度为0的点?不可能,因为图有N个点,N-1条边,所以一定有入度为0的点(否则成环,但边数不够成环,所以一定有入度为0的点,且至少一个)。 但是,注意:反向图可能有多个入度为0的点,那就说明有多个连通分量(每个连通分量至少有一个入度为0的点),所以图不连通,因此不存在一个汇点。 所以算法: 1. 构建反向图(同时记录反向图的入度,反向图的入度就是原图的出度)。 2. 统计反向图中入度为0的点的个数: - 如果个数为0:不可能(因为N个点,N-1条边,不可能没有入度为0的点)。 - 如果个数大于1:则输出-1(因为多个连通分量)。 - 如果个数为1:那么从该点出发进行BFS/DFS遍历整个反向图,如果遍历到的节点数等于N,则这个点就是答案;否则,图不连通,输出-1。 注意:题目要求编号最小的,但是如果有多个入度为0的点,直接输出-1;如果只有一个,那么那个点就是唯一的候选,所以直接输出它。但是,在反向图中,入度为0的点只有一个,所以不需要比较编号大小(只有一个点)。但是,如果存在多个连通分量,那么入度为0的点不止一个,我们直接输出-1。 所以,我们只需要: - 统计反向图中入度为0的点的个数和具体点。 - 如果个数不为1,则输出-1。 - 如果为1,则从该点开始遍历,如果遍历的节点数等于N,则输出该点;否则输出-1。 但是,还有一种情况:反向图可能有多个连通分量,但我们只统计了一个入度为0的点?不会,因为每个连通分量至少有一个入度为0的点。所以如果入度为0的点只有一个,那么整个反向图只有一个连通分量,那么从该点出发应该能遍历所有点(因为是连通图)。所以实际上,我们只需要判断入度为0的点是否只有一个,然后遍历验证连通性。但理论上,在树中(N个点,N-1条边),如果只有一个入度为0的点,那么它一定是连通的(因为边数正好是N-1,所以连通图就是树)。所以我们可以不进行遍历,但题目没有保证没有环?注意题目中边是单向的,而且只有N-1条边,所以不可能有环(因为环至少需要n条边)。所以反向图是一个有向无环图,且边数为N-1。如果只有一个入度为0的点,那么整个图就是一棵树,所以连通。因此,我们不需要再遍历验证连通性,因为如果入度为0的点只有一个,那么整个图就是连通的。 证明:在反向图中,有N个点,N-1条边,且只有一个入度为0的点(根),那么其他点的入度至少为1(因为边只有N-1条,所以除了根以外,每个点恰好有一个入边,所以整个图构成一棵树)。因此,连通。 所以,步骤简化: 1. 构建反向图,并记录每个点的入度(反向图的入度)。 2. 统计入度为0的点的个数: - 如果个数为1,则输出这个点; - 否则,输出-1。 但是,注意:在反向图中,入度为0的点就是原图中出度为0的点。而原图中,汇点应该是出度为0的点(因为所有其他点都能到达它,它自己不需要有出边)。所以原图中出度为0的点只能有一个,因为如果有两个,那么这两个点之间无法互相到达(即不能同时作为汇点),所以不可能同时存在两个汇点。因此,原图中出度为0的点有且只有一个,那么反向图中入度为0的点有且只有一个。 所以,我们只需要在反向图中统计入度为0的点(即原图中出度为0的点)的个数: - 如果恰好一个,输出这个点; - 否则(0个或多个),输出-1。 但是,原图中出度为0的点不可能为0个吗?因为图有N个点,N-1条边,总出度之和为N-1,所以平均每个点出度不到1,所以一定存在出度为0的点(因为如果所有点出度至少为1,则总出度至少为N,矛盾)。所以出度为0的点至少有一个。因此,我们只需要判断出度为0的点的个数:如果为1,则输出该点;如果大于1,则输出-1。 因此,算法可以更简单:不需要构建反向图,只需要在原图中统计每个点的出度。 - 遍历所有边,对于每条边a->b,那么a的出度加1(因为a指向b)。 - 最后,统计出度为0的点的个数: - 如果个数为1,则输出那个点的编号; - 如果个数大于1,则输出-1。 为什么?因为原图中,如果一个点出度为0,那么它没有出边,所以其他点必须能够到达它。但是,如果有多个出度为0的点,那么这些点之间无法互相到达(因为没有出边,所以不能到达其他点),所以不可能有一个汇点使得所有点都能到达(因为两个出度为0的点,除了自己,另一个出度为0的点无法到达它)。所以只能有一个出度为0的点。而且,这个点就是汇点。 但是,这样够吗?考虑一个例子:3个点,两条边:1->2, 3->2。那么点2的出度为0,点1和点3的出度都为1。那么出度为0的点只有2。所以输出2。而且确实,1和3都能到达2。 再考虑一个例子:3个点,两条边:1->2, 2->3。那么点3出度为0,点1和2的出度分别为1和1。输出3。而且1和2都能到达3。 再考虑一个反例:3个点,两条边:1->2, 1->3。那么点1的出度为2,点2和3的出度为0。那么出度为0的点有两个(2和3)。所以输出-1。正确,因为2和3之间不能互相到达,1能到达2和3,但是2不能到达3,3不能到达2,所以没有汇点。 但是,还有一个问题:如果存在一个点,它出度不为0,但是其他点无法到达它,那么它就不可能是汇点。而我们要求所有点都能到达汇点。在上面的简单统计中,我们只关注了出度为0的点,因为只有出度为0的点才可能是汇点(因为如果有出边,那么它就可以到达其他点,那么其他点就不能通过它作为终点,因为要求终点是汇点,即终点不能有出边到达其他点,所以汇点必须出度为0)。所以汇点必须是出度为0的点。 那么,如果只有一个出度为0的点,是否一定所有点都能到达它?考虑下面例子:4个点,边为:1->2, 2->3, 3->2。注意,这里出现了环:2->3->2,然后还有一条边1->2。那么点1的出度是1,点2的出度是1(因为2->3),点3的出度是1(3->2),点4的出度?这里没有点4,所以是3个点?我们重新设计:3个点,边:1->2, 2->3, 3->2。那么点1的出度1,点2的出度1,点3的出度1。那么没有出度为0的点?但是图有3个点,2条边?不对,这里我们写了3条边。所以题目只有N-1条边,所以这里边数应该是2条。所以重新设计:3个点,2条边:1->2, 3->2(这样就没有环了,且出度为0的点只有2)。所以题目中不会出现环,因为边数只有N-1条,所以不可能形成环(环至少需要N条边?不对,环至少需要n条边?对于n个点,环至少需要n条边?不对,环可以只有3条边(3个点形成一个三角环),但这里只有2条边,所以不可能形成环。所以图是无环的,因为边数不够。 因此,图是一个有向无环图(DAG),且边数N-1。所以整个图可能是一个有向森林(多个连通分量)或者一棵树。每个连通分量是一棵有向树(可能有多个根,即多个入度为0的点,但在原图中,我们关心的是出度为0的点)。 因此,结论:我们只需要统计原图中出度为0的点的个数。如果个数为1,则输出该点;否则输出-1。 但是,考虑连通性:如果图不连通,那么可能会有多个出度为0的点(每个连通分量至少有一个出度为0的点),所以输出-1。如果连通,那么整个图是一棵树,那么出度为0的点只能有一个(即叶子节点,但树中可能有多个叶子节点?注意:有向树中,叶子节点是出度为0的点。但是,在一棵有向树中,叶子节点可以有多个,但根只有一个(入度为0)。而这里我们要求的是汇点,汇点必须是所有点都能到达的点,所以汇点必须是树根?不对,在有向树中,边是从根指向叶子。那么所有点都能到达叶子节点吗?不一定,因为只能从根到叶子,而叶子节点之间不能互相到达。所以汇点应该是叶子节点?不对,应该是根节点?不对,因为根节点只能到达其他点,而其他点不能到达根节点(因为边是根指向叶子)。所以方向反了。 所以,我们之前用反向图的思想是正确的。所以还是应该用反向图。 重新思考:原图中,要求所有点都能到达汇点S。那么,在反向图中,我们要求从S出发能到达所有点(因为反向图中,原图的边反向,所以原图中到达S的路径在反向图中就是从S出发的路径)。因此,在反向图中,S必须是一个根节点(入度为0),并且从S出发能遍历所有点(即反向图连通)。所以反向图中,入度为0的点(即原图中出度为0的点)只能有一个,且这个点就是我们要找的S。但是,反向图是否连通?如果反向图连通,那么S就能到达所有点。所以我们需要判断反向图的连通性。 但是,由于边数只有N-1,所以如果反向图中入度为0的点只有一个,那么整个反向图就是一棵以该点为根的有向树,所以连通。因此,我们只需要判断反向图中入度为0的点是否只有一个。 所以,算法: 1. 读入N,然后N-1条边。 2. 初始化一个数组outdeg(原图的出度)或者用indeg_rev(反向图的入度)来统计。实际上,我们统计反向图的入度:即每个点作为终点被指向的次数(也就是原图中每个点的入度?不对,反向图:原边a->b,反向图是b->a,所以反向图中,b的入度是指有多少条边指向b?不对,在反向图中,我们关心的是每个点在反向图中的入度(即原图中该点的出度)。所以,我们可以直接统计原图中每个点的出度:对于每条边a->b,那么a的出度加1(表示a有一条出边)。 所以,实际上,我们不需要显式构建反向图,只需要统计原图中每个点的出度(因为反向图的入度就是原图中该点的出度)。然后,我们找原图中出度为0的点(即反向图中入度为0的点)的个数: - 如果个数为1,则输出那个点; - 否则,输出-1。 所以,代码步骤: 1. 初始化一个数组outdegree,长度为N+1(下标1~N),初始为0。 2. 读入N-1条边,对于每条边(a, b): outdegree[a]++ (因为a指向b,所以a的出度加1) (注意:b的入度我们不需要,因为我们不关心入度) 3. 统计outdegree数组中,值为0的点的个数和具体点。 - 如果个数为0:不可能(因为总出度之和为N-1,所以至少有一个点出度为0)。 - 如果个数为1:则输出那个点的编号。 - 如果个数大于1:输出-1。 但是,考虑一个特殊情况:只有一个点(N=1)。那么没有边(N-1=0),所以该点的出度为0。所以个数为1,输出1。 所以,这个算法可以处理所有情况。 验证: 例子1:N=3,边:1->2, 3->2。那么: outdegree[1]=1, outdegree[3]=1, outdegree[2]=0。所以出度为0的点只有2,输出2。 例子2:N=3,边:1->2, 2->3。那么: outdegree[1]=1, outdegree[2]=1, outdegree[3]=0。输出3。 例子3:N=3,边:1->2, 1->3。那么: outdegree[1]=2, outdegree[2]=0, outdegree[3]=0。出度为0的点有两个(2和3),输出-1。 因此,代码实现: 注意:输入格式:第一行一个整数N,接下来N-1行,每行两个整数a,b,表示一条从a到b的单向边。 代码: ```python n = int(input().strip()) outdegree = [0] * (n+1) # 0号位置不用,从1到n for i in range(n-1): a, b = map(int, input().split()) outdegree[a] += 1 # 统计出度为0的点 candidates = [] for i in range(1, n+1): if outdegree[i] == 0: candidates.append(i) if len(candidates) == 1: print(candidates[0]) else: print(-1) ``` 但是,题目要求输出编号最小的?这里candidates可能有多个,但我们在统计的时候,如果个数为1,那么只有一个候选;如果个数大于1,输出-1。所以不需要比较大小。 但是,如果题目要求如果有多个候选,输出编号最小的?但是题目要求:如果有多个这样的站点,输出编号最小的;如果没有,输出-1。而我们的算法中,当有多个出度为0的点时,我们输出-1,因为不可能有汇点。所以符合题意。 因此,代码如上。 但是,我们是否漏掉了什么?考虑图不连通的情况,比如两个连通分量,每个连通分量都有一个出度为0的点。那么我们就输出-1,正确。 但是,还有一种情况:如果图是连通的,但是有多个出度为0的点?不可能,因为连通的有向图(树)只能有一个根(入度为0),但出度为0的点(叶子)可以有多个。但是,我们要求的是汇点,汇点必须出度为0,而且所有点都能到达它。在树中,只有叶子节点出度为0,但是叶子节点可能不止一个,而且除了根节点,其他节点不能到达根节点(如果边是从根指向叶子)。所以方向反了。 所以,我们之前用反向图的思想,实际上应该统计反向图的入度(即原图的出度)为0的点,并且要求只有一个。但是,在树中,叶子节点可以有多个,所以如果原图是一棵正常的树(边从根指向叶子),那么原图中出度为0的点(叶子)有多个,所以输出-1。但是,题目要求的汇点应该是叶子节点吗?不,汇点应该是所有点都能到达的点,所以应该是叶子节点。但是,多个叶子节点之间不能互相到达,所以不可能有一个汇点使得所有点都能到达它。所以输出-1正确。 那么,什么时候会有汇点?当图是一棵反向树:即所有边都指向根节点(即根节点是汇点)。那么原图中,根节点的出度应该是0(因为根节点没有出边,其他点都有出边指向根节点)。所以原图中,根节点出度为0,其他点出度为1。所以出度为0的点只有根节点一个。因此,这种情况符合我们的算法。 所以,算法正确。 因此,我们只需要按照上述代码实现。 但是,题目要求使用中文回答问题,并且在最后输出3-5个相关问题。 所以,最终回答: 我们使用统计原图中每个点的出度的方法,如果出度为0的点恰好有一个,则输出该点;否则输出-1。代码实现如上。 相关问题: §§相关问题§§ 1. 如果题目要求的是源点(即从该点可以到达所有其他点)而不是汇点,应该如何修改算法? 2. 如果图中存在环,且边数可能大于N-1,那么如何寻找汇点(即所有其他点都能到达的点)? 3. 在本题中,为什么只需要统计出度为0的点的个数而不需要检查连通性? 4. 如果题目要求输出所有可能的汇点(如果有多个),并按照编号升序输出,应该如何修改算法? 5. 如果图是无向图,那么如何找到所有点都能到达的点(即中心点)?</think>### 题目分析 题目要求在有向图中找到一个汇点(所有其他点都能到达的点)。图中有 $N$ 个节点(编号 $1$ 到 $N$)和 $N-1$ 条单向边。关键点: 1. **汇点特性**:汇点必须是出度为 $0$ 的点(因为它没有出边,其他点必须能到达它)。 2. **唯一性**:若存在多个出度为 $0$ 的点,则它们互相不可达,无法同时作为汇点,此时无解。 3. **连通性**:边数 $N-1$ 保证图中无环,若出度为 $0$ 的点恰好有一个,则整个图连通(树形结构),该点即为汇点。 ### 算法步骤 1. 统计每个节点的出度(即从该节点出发的边数)。 2. 遍历所有节点,找出所有出度为 $0$ 的点: - 若有且仅有一个,输出该点。 - 若有多个或不存在,输出 $-1$(实际不可能不存在,因总出度=$N-1$)。 ### 代码实现 ```python n = int(input().strip()) out_degree = [0] * (n + 1) # 初始化出度数组(索引1~n) # 读取N-1条边,更新出度 for _ in range(n - 1): a, b = map(int, input().split()) out_degree[a] += 1 # a有一条出边指向b # 找出所有出度为0的点 sinks = [] for i in range(1, n + 1): if out_degree[i] == 0: sinks.append(i) # 判断结果 if len(sinks) == 1: print(sinks[0]) else: print(-1) ``` ### 示例说明 - **输入**: `3` `1 2` `3 2` - **处理**: 节点1出度=1,节点2出度=0,节点3出度=1。 唯一出度为0的点是节点2,输出 `2`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值