例题
\(HDU1272\) 小希的迷宫
\(AcWing\) \(4290\). 小希的迷宫
题目要求:判断是不是连通无环的图
总结
1、必须使用\(scanf\)!使用\(cin\)直接\(TLE\)!
2、无向图判环,应尽量采用并查集(判断连通性)+公式法(判环)
3、
方法1:数学公式法判环+并查集判连通性
判断一个图是否为连通图且没有环其实就一个条件,点数-1=边数,但是需要特判(0,0)
此方法可以通过AcWing+HDU
方法2:并查集
- 并查集判环,不用真的建图
- 因为不一定每个点都用上,所以需要记录本轮使用了哪些节点
- 检查是否有环:
判断输入边的两个点,如果它们已经在同一个家族内,说明有环 - 无环情况下,还需要检查是不是连通图。
这时,需要判断家族数量是不是\(1\)
此方法可以通过AcWing+HDU
方法3:\(DFS\)标记白灰黑
-
\(DFS\)判环,需要使用邻接表建图
-
因为不一定每个点都用上,所以需要记录本轮使用了哪些节点
-
检查是否有环:
(1)枚举每个节点,找到一个使用过的节点进去开始深搜
(2)需要单开一个状态数组\(vis\),来记录哪些节点深搜过了 -
在无环情况下,还需要判断是不是连通
枚举每个节点,如果出现过,但深搜没有搜索过,就意味着不连通
此方法HDU可AC,AcWing会SE,原因可能是因为内存过大。
并查集+公式
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
bool st[N];
bool flag;
int edges, points;
//最简并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
//合并集合
bool join(int a, int b) {
if (find(a) == find(b)) return false;
p[find(a)] = find(b);
return true;
}
//初始化并查集
void init() {
for (int i = 0; i < N; i++) p[i] = i;
//初始化状态数组
memset(st, false, sizeof(st));
flag = true;
edges = points = 0;
}
int main() {
int a, b;
//初始化
init();
while (~scanf("%d%d", &a, &b), ~a && ~b) {
if (a && b) {
edges++; //增加一条边
if (!st[a]) {
st[a] = true;
points++;
}
if (!st[b]) {
st[b] = true;
points++;
}
if (!join(a, b)) flag = false;
} else {
//这个是应付AcWing的黑心数据 0 0 -1 -1而加的特判
//如果连通,并且,无环
flag && (edges == points - 1 || edges == 0) ? puts("Yes") : puts("No");
init();
}
}
return 0;
}
并查集+枚举
#include <bits/stdc++.h>
using namespace std;
const int N = 100500;
int n, m; // n个顶点,m条边
bool flag = true; //默认是合格的,如果有环是false,不连通也是false
//每组数据,是否符合要求:1、任意两点间道路唯一,2、需要是连通的
bool st[N]; //节点是不是出现过
//最简并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
//合并集合
bool join(int a, int b) {
if (find(a) == find(b)) return false;
p[find(a)] = find(b);
return true;
}
// 初始化各种状态数组和标识数据
void init() {
flag = true;
//初始化并查集
for (int i = 0; i < N; i++) p[i] = i;
//清空状态数组
memset(st, 0, sizeof st);
}
int main() {
int a, b;
//初始化
init();
while (~scanf("%d%d", &a, &b), ~a && ~b) {
if (a && b) { //一组数据输入进行中
st[a] = st[b] = true; //标识a,b两个点走过了
if (!join(a, b)) //如果两个点在一个集合中,说明有重复路线出现~
flag = false; //标记已经不合法
} else {
//单组数据输入结束
//如果没有检查到环,才有资格检查是不是连通
if (flag) {
int cnt = 0;
for (int i = 1; i < N; i++) {
if (st[i] && find(i) == i) cnt++; //判断根节点cnt数目
if (cnt > 1) {
flag = false;
break;
}
}
}
//检查通过
flag ? puts("Yes") : puts("No");
//清空准备下一次输入
init();
}
}
return 0;
}
dfs
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N << 1;
/*
7e5+10
=700000*4/1024/1024=2.67MB
这内存也不大啊,为啥会SE呢?不是说64MB的上限吗?
被卡的好难受,这是逼我去用并查集+公式解题啊!
*/
//邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool st[N]; //标识某个节点号是不是使用过
bool vis[N]; //是不是在dfs过程中搜索过
int flag; // true:满足两个条件 false:不满足两个条件 (1)是不是有环,要求无环 (2)是不是连通,要求连通
// dfs无向图判环
void dfs(int u, int fa) { //从u节点开始搜索,它的父节点是fa,输入fa的意义在于不能走回头路,防止死循环
vis[u] = true; //标识u已经搜索过了
for (int i = h[u]; ~i; i = ne[i]) { //枚举u的每一条出边
int j = e[i];
if (j == fa) continue; //放过来源节点
if (vis[j]) { //如果j被搜索过,说明有环
flag = false; //标识有环
return;
}
dfs(j, u); //继续搜索j节点,它的父节点是u
}
}
//每组输入的清空与初始化
void init() {
memset(st, 0, sizeof(st));
memset(vis, 0, sizeof(vis));
memset(h, -1, sizeof h);
idx = 0;
}
int main() {
int a, b;
init();
while (scanf("%d%d", &a, &b) && ~a && ~b) {
if (a && b) { //如果输入的a,b是两个非零数字
st[a] = st[b] = true; //标识a,b已使用
add(a, b), add(b, a); //添加两条无向边
} else { //如果输入的是两个0,描述是一组数据输入结束
flag = true; //默认是无环+连通图
// 1、检查是不是无环
for (int i = 1; i < N; i++) //枚举每个节点
if (st[i]) { //如果i节点使用过
//从找到的第一个使用过的节点开始进行dfs扫描
dfs(i, i);
break;
}
// 2、检查连通性
if (flag) { //扫描完的结果:1、找到了环,2、没有找到环
//下面将现次枚举每个使用过的点,查看它是不是在dfs过程中被扫描到了,如果存在使过未扫到的点,则说明不连通
for (int i = 1; i < N; i++)
if (st[i] && !vis[i]) { //如果存在出现过,但dfs没有扫描到的点,说明不连通
flag = false;
break;
}
}
flag ? puts("Yes") : puts("No"); //两个条件都满足,输出Yes,否则输出No
init(); //因为要输入下一组数据,需要清空状态数组
}
}
return 0;
}