蒟蒻来讲题,还望大家喜。若哪有问题,大家尽可提!
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 1≤N≤105
-
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)为例
今天就到这里了!
大家有什么问题尽管提,我都会尽力回答的!最后,祝大家新年快乐!
吾欲您伸手,点的小赞赞。吾欲您喜欢,点得小关注!