無向圖判環+判連通性
阿新 • • 發佈:2022-05-16
例題
\(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;
}