强连通分量

1|0有向图的强连通分量

1|1基本概念

1|0连通分量:

对于分量内任意两点uv , 必然可以找到从 u 走到 v 且可以从 v 走到 u.

1|0强连通分量:

极大连通分量(包含点数最多)

强连通分量常用于缩点

1|2Tarjan算法:

基于 DFS :

1|0Tarjan算法几个重要概念:

在已经DFS的树中:

  1. 后前边: (x, y) x是y的一个祖先, 但存在一条由y->x的边.
  2. 横插边: (x, y) x和y不属于同一条分支, 但存在一条y->x的边
  3. 前向边: (x, y) y是x的祖先, 存在一条x->y的边

1|0几个数组和变量:

  1. 时间戳: 记录搜索到每个点的时间.即对每个点根据搜索顺序进行标号排序.
  2. dfn数组: 记录每个点的时间戳.(同时具有判重数组作用)
  3. low数组: 记录每个点向上走所能达到的最高点(即时间戳最小点).
  4. stk栈: 记录当前强连通分量内的点.
  5. id数组: 记录每个点所在的连通分量.
  6. scc_cnt: 强连通分量个数

1|0模板:

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); } }

2|0例题

2|1AcWing 1174. 受欢迎的牛

1|0算法思路 :

强连通分量的典型应用:缩点. 将整个图缩点后,得到一张拓扑图, 求出强连通分量后, 寻找出度为0的节点, 该节点内的牛的数量为答案(注意出度为0的点只能有一个,否则结果为0)

#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; }

2|2AcWing 367. 学校网络

1|0算法思路:

强连通分量缩点, 将原图转化为拓扑图, 第一问求入度为0的点的个数, 第二问结论: max(cntin,cntout)

#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; }

2|3AcWing 1175. 最大半连通子图

#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; }

2|4AcWing 368. 银河

1|0算法思路:

强连通分量求解差分约束问题:
由强连通分量进行缩点, 求缩点后的拓扑图, 对拓扑图求最长路

#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; }

__EOF__

本文作者lhqwd
本文链接https://www.cnblogs.com/lhqwd/p/14552950.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   lhqwd  阅读(139)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示