ACWing 4493 -- 思维题&&并查集&&dfs
题目描述
思路
对于一个无向图中的 简单环(环中边的数量等于点的数量),有一个很强的性质:每个点的度数等于 \(2\)。
那么我们只需要先找出所有的连通块,然后判断每个连通块中的每条边的度数是否位 \(2\) 就可以判断该点所在连通块是不是一个简单环了。
找连通块
求连通块问题是一个图论中的经典问题,我们有三种方法:
- 并查集
- \(dfs\)
- \(bfs\)
其中并查集最简单,代码最短,因此对于求连通块问题优先考虑并查集
代码1:并查集
依据性质:判断每个点的度数是否为2
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200010;
int n, m;
int pre[N];
bool st[N]; // st[i]=false -> 连通块 i 是简单环,= true -> 不是简单环
int d[N];
int find(int x)
{
if(pre[x] == x) return x;
return pre[x] = find(pre[x]);
}
int main()
{
for(int i = 0; i < N; i ++ ) pre[i] = i;
cin >> n >> m;
// first. 找到所有连通块,计算每个点的度数
while(m -- )
{
int a, b;
cin >> a >> b;
pre[find(a)] = find(b); // a<-->b,合并到同一个连通块
d[a] ++ ; // 度数 ++
d[b] ++ ; // 度数 ++
}
// 不要傻乎乎的去真的遍历“每个连通块中”的所有点
// 那样的话我们还要找到任意连通块中的点集合
// 只需要直接遍历所有点来判断每个点的度数是否为2就行
for(int i = 1; i <= n; i ++ )
{
if(d[i] != 2)
{
st[find(i)] = true;
}
}
int res = 0;
for(int i = 1; i <= n; i ++ )
{
if(pre[i] == i && !st[i]) res ++ ;
}
cout << res << endl;
return 0;
}
代码2:dfs:
判断每个点是否连了两条边
这其实是对性质:简单环中每个点的度数为 \(2\) 的另外一种表达形式。
每个点的度数为 \(2\) 就等价于每个点连了 \(2\) 条边。
我们这里通过 \(dfs\) 来判断
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010, M = N << 1;
int h[N], e[M], ne[M], idx;
int n, m;
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u)
{
// 标记为已经遍历过,需要放在 dfs[e[i]] 的前面
st[u] = true;
bool has_res = true;
int cnt = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
cnt ++ ;
if(!st[e[i]] && !dfs(e[i])) has_res = false; // 需要继续dfs下去直到遍历完整个连通块
}
return has_res && (cnt == 2);
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m -- )
{
int a, b;
cin >> a >> b;
add(a, b); add(b, a);
}
// 判断每个点是否连了两条边
int res = 0;
for(int i = 1; i <= n; i ++ )
{
if(st[i]) continue;
res += dfs(i);
}
cout << res << endl;
return 0;
}