判断图中存在闭环的常用方法——以Atcoder Beginner Contest 285(D - Change Usernames)为例

蒟蒻来讲题,还望大家喜。若哪有问题,大家尽可提!

Hello, 大家好哇!本初中生蒟蒻今天以AtCoder Beginner Contest 285的D题——Change Usernames为例,给大家讲解一下判断图中存在闭环的常用方法!

===========================================================================================

判断图中存在闭环的常用方法

判断图中有闭环的常用方法主要有三种:DFS,并查集,拓扑排序。具体介绍如下~~~

DFS方法

我们可以将每个点连接起来,故形成一个图。然后,通过DFS将每个点枚举一遍,若当前点已经走过一遍了,就说明存在闭环,否则继续枚举(如下图)。

当点多边少的时候,用邻接表;反之,当点少边多的时候用邻接矩阵。想必大家DFS都会了,在此就不累述了哈~~~

并查集方法

并查集是将联通的节点并入一个值为父节点下标的集合,那么如果一个节点要连接的时候它已经在这个集合里了,就说明是个环(可能有点难理解,看下面一个例子就好了~~~)
在这里插入图片描述
如上图,当4试图去加入2所在的集合时,发现4已经在集合中了,所以4和2构成了回路。

拓扑排序方法

首先,我们说一下入度出度的概念,顶点的度是指和该顶点相连的边的条数。特别是对于有向图来说,顶点的出边条数称为该顶点的出度,顶点的入边条数称为该顶点的入度
然后,我们来讲一下如何利用拓扑排序来判断回路,拓扑排序其实就是一个宽搜的过程,每次找入度为0的点并将此点放入一个队列之中,删掉当前点所有连接的边并且把此点指向的点的入度减1,之后依次枚举当前指向的点,判断是否入度为0,如果是0,再放入队列,以此类推……直到所有节点入度都不为0或者是所有点都枚举完了。
最后,判断如果还有入度不为0的点,就说明有环,反之则无(不是很好理解,看个例子吧~~~)

首先,因为点1的入度为0,所以把点1入队:
在这里插入图片描述

然后,我们把点1所连接的边删掉并把点2的入度减1,并且枚举到点2,但是因为点2的入度不为0,所以不把他入队。
在这里插入图片描述最后,队列中的点数小于总节点数,说明存在回路。

说明

前面讲的图皆为有向图,若为无向图,可以看作特殊的有向图。


实例讲解(Atcoder Beginner Contest 285 D - Change Usernames)

问题描述

Problem Statement

You run a web service with N N N users.

The i i i-th user with a current handle S i S_{i} Si​ wants to change it to T i T_{i} Ti​.
Here, S 1 S_{1} S1​,…, and S N S_{N} SN are pairwise distinct, and so are T 1 T_{1} T1​,…, and T N T_{N} TN​.

Determine if there is an appropriate order to change their handles to fulfill all of their requests subject to the following conditions:

  • you change only one user’s handle at a time;
  • you change each user’s handle only once;
  • when changing the handle, the new handle should not be used by other users at that point.

Constraints

  • 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1N105
  • S i ​ S_{i}​ Si and T i ​ T_{i}​ Ti are strings of length between 1 and
    8 (inclusive) consisting of lowercase English letters.
  • S i ​ ≠ T i S_{i}​\neq T_{i} Si=Ti
  • S i S_{i} Si​ are pairwise distinct.
  • T i T_{i} Ti​ are pairwise distinct.

Input

The input is given from Standard Input in the following format:

N
S1​  T1
S2​  T2​
⋮
SN​  TN​

Output

Print Yes if they can change their handles to fulfill all of their requests subject to the conditions; print No otherwise.

Sample Input 1

2
b m
m d

Sample Output 1

Yes

The 1-st user with a current handle b wants to change it to m.
The 2-nd user with a current handle m wants to change it to d.
First, you change the 2-nd user’s handle from m to d; then you change the 1-st user’s handle from b to m. This way, you can achieve the objective.

Note that you cannot change the 1-st user’s handle to m at first, because it is used by the 2-nd user at that point.

Sample Input 2

3
a b
b c
c a

Sample Output 2

No

The 1-st user with a current handle a wants to change it to b.
The 2-nd user with a current handle b wants to change it to c.
The 3-rd user with a current handle c wants to change it to a.
We cannot change their handles subject to the conditions.
Sample Input 3

5
aaa bbb
yyy zzz
ccc ddd
xxx yyy
bbb ccc

Sample Output 3

Yes

解题思路

首先,这道题最最最最最……关键的一步就是要知道可以用图中判断回路的方法来做!!!把原来的S到T连一条线,之后若出现环,就说明肯定无法达到有两个字符串不同(在此,不证明了~~~嘿嘿嘿),反之就一定可以。那么这题就可以套用前面讲的3种方法了……

DFS代码
#include <iostream>
#include <unordered_map>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
string s[N], t[N];
unordered_map<string, int> h;
string e[N];
int idx, ne[N];
unordered_map<string, int> st;
bool res = 1;

void add(string a, string b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(string u)
{
	if (st[u]) return;
	st[u] = 1;
	
	for (int i = h[u]; ~i; i = ne[i])
	{
		if (st[e[i]] == 1) res = 0;
		else if (st[e[i]] != 2)
			dfs(e[i]);
	}
	
	st[u] = 2;
}

int main()
{
	cin >> n;
	
	for (int i = 1; i <= n; i ++)
		cin >> s[i] >> t[i], h[s[i]] = h[t[i]] = -1;
	
	for (int i = 1; i <= n; i ++)
		add(s[i], t[i]);
		
	for (int i = 1; i <= n; i ++) dfs(s[i]);
	
	(res) ? puts("Yes") : puts("No");

	return 0;
}
并查集代码
#include <iostream>
#include <unordered_map>

using namespace std;

const int N = 1e5 + 10;

int n;
string s[N], t[N];
unordered_map<string, string> p;

string find(string x)
{
	if (p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main()
{
	cin >> n;
	
	for (int i = 1; i <= n; i ++)
		cin >> s[i] >> t[i], p[s[i]] = s[i], p[t[i]] = t[i];
		
	for (int i = 1; i <= n; i ++)	
	{
		if (find(s[i]) == find(t[i]))
		{
			cout << "No" << endl;
			return 0;
		}
		p[find(s[i])] = find(t[i]);
	}
	
	cout << "Yes" << endl;

	return 0;
}
拓扑排序代码(BFS宽搜)
#include <iostream>
#include <unordered_map>
#include <unordered_set>

using namespace std;

const int N = 2e5 + 10;

int n;
string s[N], t[N];
unordered_map<string, int> d;
string q[N];
unordered_map<string, int> h;
string e[N];
int idx, ne[N];
unordered_set<string> sz;

void add(string a, string b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort()
{
	int hh = 0, tt = -1;
	
	for(auto c : sz)
	{
		if (!d[c])
			q[++ tt] = c;
	}
	
	while (hh <= tt)
	{
		string t = q[hh ++];
		
		for (int i = h[t]; ~i; i = ne[i])
		{
			d[e[i]] --;
			if (!d[e[i]])
				q[++ tt] = e[i];
		}
	}
	
	return tt == (int)sz.size() - 1;
}

int main()
{
	cin >> n;
	
	for (int i = 1; i <= n; i ++){
		cin >> s[i] >> t[i], d[t[i]] ++, h[s[i]] = h[t[i]] = -1;
		if(s[i] != t[i])
			sz.insert(t[i]);
		sz.insert(s[i]);
	}
		
	
	for (int i = 1; i <= n; i ++)
		add(s[i], t[i]);
	
	if (topsort())
		cout << "Yes" << endl;
	else
		cout << "No" << endl;
	return 0;
}
时间复杂度

这三种方法在计算时间复杂度时比较复杂,但通过运算耗时来比较:
并查集时间 < D F S 的时间< 拓扑排序时间 并查集时间 < DFS的时间 < 拓扑排序时间 并查集时间<DFS的时间< 拓扑排序时间


视频讲解

判断图中存在闭环的常用方法——以Atcoder Beginner Contest 285(D - Change Usernames)为例


今天就到这里了!

大家有什么问题尽管提,我都会尽力回答的!最后,祝大家新年快乐!

吾欲您伸手,点的小赞赞。吾欲您喜欢,点得小关注!

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值