对于份量内任意两点\(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; }