對於份量內任意兩點\(u 和 v\) , 必然能夠找到從 \(u\) 走到 \(v\) 且能夠從 \(v\) 走到 \(u\).ios
極大連通份量(包含點數最多)算法
強連通份量經常使用於縮點數組
基於 \(DFS\) :網絡
在已經\(DFS\)的樹中:ide
int timecnt; //時間戳 int dfn[N]; //每一個點的時間戳 int low[N]; //low[u] : u所在的子樹中全部點中所能向上走到的時間戳最小的點 int scc_cnt; //強連通份量的數量 int id[N]; //id[i] : 表示i號點所在的強連通份量的編號 stack<int> stk; //存儲當前強連通份量裏的全部點 int in_stk[N]; //記錄該點是否在棧中 void tarjan(int u) { low[u] = dfn[u] = ++ timecnt; stk.push(u); in_stk[u] = 1; for (int i = h[u]; i != -1; i = ne[i]){ int j = e[i]; if (!dfn[j]){ //j點沒有被遍歷過,j點必定是在子樹中 tarjan(j); //遍歷j low[u] = min(low[u], low[j]); //遍歷事後的j的low可能已經找到一個更高的結點,因此要去更新u } else if (in_stk[j]) //j在棧中,則j和u之間必定是一條橫叉邊或向前邊,即j的時間戳必定比u小 low[u] = min(low[u], dfn[j]); } if (low[u] == dfn[u]){ //到此處,u的全部邊已經遍歷完,若是low[u] = dfn[u] : 獲得了一個強連通份量 scc_cnt ++; int y; //此時該強連通份量裏的點全在棧中,所有取出 do{ y = stk.top(); stk.pop(); id[y] = scc_cnt; sizes[scc_cnt] ++; }while (y != u); } }例題
強連通份量的典型應用:縮點. 將整個圖縮點後,獲得一張拓撲圖, 求出強連通份量後, 尋找出度爲0的節點, 該節點內的牛的數量爲答案(注意出度爲0的點只能有一個,不然結果爲0)spa
#include <iostream> #include <cstring> #include <queue> #include <stack> using namespace std; const int N = 1e5 + 10, M = 5e4 + 10; int h[N], e[M], ne[M], idx; int timecnt; //時間戳 int dfn[N]; //每一個點的時間戳 int low[N]; //low[u] : u所在的子樹中全部點中所能向上走到的時間戳最小的點 int scc_cnt; //強連通份量的數量 int id[N]; //id[i] : 表示i號點所在的強連通份量的編號 stack<int> stk; //存儲當前強連通份量裏的全部點 int in_stk[N]; //記錄該點是否在棧中 int sizes[N]; //強連通份量的結點數量 int dout[N]; //每一個強連通份量的出度 int n, m; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void tarjan(int u) { low[u] = dfn[u] = ++ timecnt; stk.push(u); in_stk[u] = 1; for (int i = h[u]; i != -1; i = ne[i]){ int j = e[i]; if (!dfn[j]){ //j點沒有被遍歷過,j點必定是在子樹中 tarjan(j); //遍歷j low[u] = min(low[u], low[j]); //遍歷事後的j的low可能已經找到一個更高的結點,因此要去更新u } else if (in_stk[j]) //j在棧中,則j和u之間必定是一條橫叉邊或向前邊,即j的時間戳必定比u小 low[u] = min(low[u], dfn[j]); } if (low[u] == dfn[u]){ //到此處,u的全部邊已經遍歷完,若是low[u] = dfn[u] : 獲得了一個強連通份量 scc_cnt ++; int y; //此時該強連通份量裏的點全在棧中,所有取出 do{ y = stk.top(); stk.pop(); id[y] = scc_cnt; sizes[scc_cnt] ++; }while (y != u); } } int main() { cin >> n >> m; memset (h, -1, sizeof h); for (int i = 1; i <= m; i ++ ){ int a, b; cin >> a >> b; add(a, b); } for (int i = 1; i <= n; i ++ ) if (!dfn[i]) tarjan(i); for (int i = 1; i <= n; i ++ ) for (int j = h[i]; j != -1; j = ne[j]){ int k = e[j]; int a = id[i], b = id[k]; if (a != b){ //兩者不在同一個強連通份量內,則由i -> k的邊是縮點後點一條邊,對於連通份量: a -> b dout[a] ++; } } int cnt = 0; int sum = 0; for (int i = 1; i <= scc_cnt; i ++ ) if (!dout[i]){ cnt ++; sum = sizes[i]; if (cnt > 1){ sum = 0; break; } } cout << sum << endl; return 0; }
強連通份量縮點, 將原圖轉化爲拓撲圖, 第一問求入度爲0的點的個數, 第二問結論: $ max(cnt_{in}, cnt_{out})$排序
#include <iostream> #include <cstring> #include <algorithm> #include <stack> #include <queue> using namespace std; const int N = 110, M = N * N / 2; int h[N], e[M], ne[M], idx; int timecnt; int low[N], dfn[N]; stack<int> stk; int scc_cnt; int id[N]; int in_stk[N]; int dout[N]; int din[N]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void tarjan(int u) { dfn[u] = low[u] = ++timecnt; stk.push(u); in_stk[u] = 1; for (int i = h[u]; i != -1; i = ne[i]){ int j = e[i]; if (!dfn[j]){ tarjan(j); low[u] = min(low[u], low[j]); }else if (in_stk[j]) low[u] = min(low[u], dfn[j]); } if (dfn[u] == low[u]){ scc_cnt ++; int y; do{ y = stk.top(); stk.pop(); in_stk[y] = 0; id[y] = scc_cnt; }while (y != u); } } int main() { int n; cin >> n; memset (h, -1, sizeof h); for (int i = 1; i <= n; i ++ ){ int b; while (cin >> b && b){ add(i, b); } } for (int i = 1; i <= n; i ++ ) if (!dfn[i]) tarjan(i); for (int i = 1; i <= n; i ++ ) for (int j = h[i]; j != -1; j = ne[j]){ int k = e[j]; int a = id[i], b = id[k]; if (a != b){ dout[a] ++; din[b] ++; } } int in_cnt = 0; int out_cnt = 0; for (int i = 1; i <= scc_cnt; i ++ ){ if (!dout[i]){ out_cnt ++; } if (!din[i]){ in_cnt ++; } } if (scc_cnt == 1) cout << 1 << endl << 0 << endl; else cout << in_cnt << endl << max(in_cnt, out_cnt) << endl; return 0; }
#include <iostream> #include <cstring> #include <algorithm> #include <unordered_set> #include <stack> using namespace std; const int N = 1e5 + 10, M = 2 * 1e6 + 10; typedef long long LL; int h[N], hs[N], e[M], ne[M], idx; int timecnt; int dfn[N], low[N]; stack<int> stk; int scc_cnt; int in_stk[N]; int sizes[N]; int id[N]; int f[N], g[N]; unordered_set<LL> used; int n, m, mod; void add(int h[], int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++; } void tarjan(int u) { dfn[u] = low[u] = ++timecnt; stk.push(u); in_stk[u] = 1; for (int i = h[u]; i != -1; i = ne[i]){ int j = e[i]; if (!dfn[j]){ tarjan(j); low[u] = min(low[u], low[j]); }else if (in_stk[j]) low[u] = min(low[u], dfn[j]); } if (low[u] == dfn[u]){ scc_cnt ++; int y; do{ y = stk.top(); stk.pop(); in_stk[y] = 0; id[y] = scc_cnt; sizes[scc_cnt] ++; }while (y != u); } } int main() { cin >> n >> m >> mod; memset (h, -1, sizeof h); memset (hs, -1, sizeof hs); for (int i = 1; i <= m; i ++ ){ int a, b; scanf("%d%d",&a, &b); add(h, a, b); } for (int i = 1; i <= n; i ++ ) if (!dfn[i]) tarjan(i); for (int i = 1; i <= n; i ++ ) for (int j = h[i]; j != -1; j = ne[j]){ int k = e[j]; int a = id[i], b = id[k]; if (a != b && !used.count((LL)a * 1e6 + b)){ add(hs, a, b); used.insert((LL)a * 1e6 + b); } } for (int i = scc_cnt; i ; i -- ){ if (!f[i]){ f[i] = sizes[i]; g[i] = 1; } for (int j = hs[i]; j != -1; j = ne[j]){ int k = e[j]; if (f[k] < f[i] + sizes[k]){ f[k] = f[i] + sizes[k]; g[k] = g[i]; }else if (f[k] == f[i] + sizes[k]) g[k] = (g[k] + g[i]) % mod; } } int maxn = 0; int sum = 0; for (int i = 1; i <= scc_cnt; i ++ ) if (f[i] > maxn){ maxn = f[i]; sum = g[i]; }else if (f[i] == maxn) sum = (g[i] + sum) % mod; cout << maxn << endl << sum << endl; return 0; }
強連通份量求解差分約束問題:
由強連通份量進行縮點, 求縮點後的拓撲圖, 對拓撲圖求最長路ci
#include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <stack> using namespace std; const int N = 1e5 + 10, M = 5e5 + 10; int h[N], e[M], w[M], idx, ne[M]; int hs[N]; //縮點後的表頭 int low[N], dfn[N], timecnt; int in_stk[N]; int scc_cnt; int id[N]; int sizes[N]; stack<int> stk; int dist[N]; int n, m; void add(int a, int b, int v) { e[idx] = b, ne[idx] = h[a], w[idx] = v, h[a] = idx ++; } void add1(int a, int b, int v) { e[idx] = b, w[idx] = v, ne[idx] = hs[a], hs[a] = idx ++; } void tarjan(int u) { low[u] = dfn[u] = ++timecnt; stk.push(u); in_stk[u] = 1; for (int i = h[u]; i != -1; i = ne[i]){ int j = e[i]; if (!dfn[j]){ tarjan(j); low[u] = min(low[j], low[u]); }else if (in_stk[j]) low[u] = min(low[u], dfn[j]); } if (low[u] == dfn[u]){ scc_cnt ++; int y; do{ y = stk.top(); stk.pop(); in_stk[y] = 0; id[y] = scc_cnt; sizes[scc_cnt] ++; }while (y != u); } } int main() { cin >> n >> m; memset (h, -1, sizeof h); memset (hs, -1, sizeof hs); for (int i = 1; i <= m; i ++ ){ int a, b, t; scanf("%d%d%d",&t, &a, &b); if (t == 1) add(a, b, 0), add(b, a, 0); if (t == 2) add(a, b, 1); if (t == 3) add(b, a, 0); if (t == 4) add(b, a, 1); if (t == 5) add(a, b, 0); } for (int i = 1; i <= n; i ++ ) add(0, i, 1); tarjan(0); int flag = 1; for (int i = 0; i <= n; i ++ ) for (int j = h[i]; j != -1; j = ne[j]){ int k = e[j]; int a = id[i], b = id[k]; if (a == b){ if (w[j] > 0){ flag = 0; } }else add1(a, b, w[j]); } if (flag == 0) puts("-1"); else{ for (int i = scc_cnt; i > 0; i -- ){ for (int j = hs[i]; j != -1; j = ne[j]){ int k = e[j]; dist[k] = max(dist[i] + w[j], dist[k]); } } long long res = 0; for (int i = 1; i <= scc_cnt; i ++ ) res += dist[i] * sizes[i]; cout << res << endl; } return 0; }