图论专题-学习笔记:强连通分量

  • 一些 update
  • 1. 前言
  • 2. 定义
  • 3. 求法
  • 4. 应用
  • 5. 总结

一些 update

update on 2021/8/12:增加了对于 Kosaraju 算法优势的分析。

1. 前言

强连通分量,是图论的一个东西。

这个东西可以将有向图变为一张 DAG,而在 DAG 上就可以使用各种技巧了。

2. 定义

分量的定义:在一张给定的有向图中,如果点 a,ba,ba,b 能够互相到达,就称点 a,ba,ba,b 在一个分量中。

显然的,分量具有传递性,即若 a,ba,ba,b 在一个分量,b,cb,cb,c 在一个分量,那么 a,ca,ca,c 也在一个分量。

强连通分量:对于一张图 GGG,设其点集为 VVV,如果能够从中选出一些点构成点集 V′V'V′,满足 V′V'V′ 内的所有点都在一个分量内,且 V′V'V′ 内的所有点都不与 V′V'V′ 之外的点在一个分量内,那么点 V′V'V′ 称作图 GGG 的一个强连通分量。

根据定义可以知道,强连通分量里面的所有点都是能够互相到达的。

3. 求法

强连通分量有很多求法,而笔者使用的求法是 Kosaraju 算法,该算法采用两次 dfs 确定强连通分量,也就是俗称的两遍 dfs 求强连通分量。

该算法的步骤如下:

  • 首先先对整张图做一遍 dfs(任意点开始均可),对于每一个点,当遍历完其所有出边之后将这个点压入一个栈 stastasta。
  • 然后将 stastasta 反转。
  • 从 stastasta 的第一个元素开始,建立一张反向图,在反向图中做一遍 dfs,此时从一个点能够遍历到的所有点属于一个强连通分量。

注意第三步是有顺序的,也就是反转 stastasta 之后从头开始选择点遍历,每个点只遍历一次。

那么这个算法为什么是正确的呢?

假设反转前的 sta={a,b,c,...,z}sta=\{a,b,c,...,z\}sta={a,b,c,...,z}。

那么反转之后 zzz 是第一个,假设在反向图中 zzz 能到达 a,b,ca,b,ca,b,c。

那么显然的,此时在正向图中能够保证 a,b,ca,b,ca,b,c 能够到达 zzz。

但是为什么 zzz 能够到达 a,b,ca,b,ca,b,c 呢?

因为在反转前的 stastasta 中,a,b,ca,b,ca,b,c 在 zzz 前面。

这就意味着 a,b,ca,b,ca,b,c 必须在 zzz 之前进栈,而在 zzz 之前进栈,要么是 a,b,ca,b,ca,b,c 与 zzz 不连通,但是这种情况已经被排除了。

因此,只能是 zzz 能够到达 a,b,ca,b,ca,b,c,这样才能够说明 a,b,ca,b,ca,b,c 在 zzz 前面。

证毕。

关于这一部分的代码可以看应用的缩点代码,这里面包含了强连通分量的代码。

除了 Kosaraju 算法之外,最常用的求强连通分量的算法就是 Tarjan 算法了。

那么 Kosaraju 算法有什么优点呢?

  • 思路比 Tarjan 算法简洁易懂。
  • 代码不容易出错。
  • 有一个很神奇的特性,这个特性将会在应用板块中讲解。

而且特别的,由于求强连通分量的 Tarjan 算法和求割点与桥的 Tarjan 算法在实现细节上是有一定不同的,有些时候容易将两者搞混,因此笔者只讲述 Kosaraju 算法。

4. 应用

强连通分量有 2 个应用,一个是缩点,一个是 2 - SAT,此处只讲缩点。

例题:P3387 【模板】缩点

强连通分量的一个好处是能够将一张有向图变为一张 DAG。

  • 怎么变呢?

因为一个强连通分量里面的所有点互相可达,因此我们可以将这些点的信息合并成一个点,那么在这么做之后这张图就会变成一张 DAG。

  • 为什么是 DAG?

假设图中还有环,那么这些点就属于一个强连通分量,但是我们已经将所有的强连通分量缩成一个点了,矛盾。

  • 缩点有什么用吗?

DAG 最好用的地方就是拓扑排序!利用拓扑排序 + DP,可以解决很多图上问题。

代码:

/*
========= Plozia =========Author:PloziaProblem:P3387 【模板】缩点Date:2021/4/15
========= Plozia =========
*/#include <bits/stdc++.h>
using std::queue;typedef long long LL;
const int MAXN = 1e4 + 10, MAXM = 1e5 + 10;
int n, m, val[MAXN], cnt_EEdge = 1, a[MAXN], HHead[MAXN], cnt_Edge = 1, sta[MAXN], Top, col, Color[MAXN], Head[MAXN];
int cnt[MAXN], ans, f[MAXN];
struct node { int to, val, Next; } EEdge[MAXM << 1], Edge[MAXM << 1];
bool vis[MAXN];int read()
{int sum = 0, fh = 1; char ch = getchar();for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);return sum * fh;
}
void add_EEdge(int x, int y, int z) { ++cnt_EEdge; EEdge[cnt_EEdge] = (node){y, z, HHead[x]}; HHead[x] = cnt_EEdge; }
void add_Edge(int x, int y, int z) { ++cnt_Edge; Edge[cnt_Edge] = (node){y, z, Head[x]}; Head[x] = cnt_Edge; }
//千万注意不能搞错原图和新图的存储!!!
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }void dfs1(int now)
{vis[now] = 1;for (int i = HHead[now]; i; i = EEdge[i].Next){int u = EEdge[i].to;if (EEdge[i].val == 0) continue;if (vis[u]) continue; dfs1(u);}sta[++Top] = now;//进栈
}void dfs2(int now, int col)
{vis[now] = 1; Color[now] = col;//染色,记录是哪一个强连通分量for (int i = HHead[now]; i; i = EEdge[i].Next){int u = EEdge[i].to;if (vis[u] || EEdge[i].val == 1) continue;dfs2(u, col);}
}void Topsort()//拓扑排序 + DP
{queue <int> q;for (int i = 1; i <= col; ++i)if (cnt[i] == 0) q.push(i), ans = Max(ans, f[i] = a[i]);while (!q.empty()){int x = q.front(); q.pop();for (int i = Head[x]; i; i = Edge[i].Next){int u = Edge[i].to; --cnt[u];f[u] = Max(f[u], f[x] + a[u]);ans = Max(ans, f[u]);if (cnt[u] == 0) q.push(u);}}
}int main()
{n = read(), m = read();for (int i = 1; i <= n; ++i) val[i] = read();for (int i = 1; i <= m; ++i){int u = read(), v = read();add_EEdge(u, v, 1); add_EEdge(v, u, 0);}for (int i = 1; i <= n; ++i)if (!vis[i]) dfs1(i);//一次 dfsmemset(vis, 0, sizeof(vis));std::reverse(sta + 1, sta + Top + 1);//注意反转!for (int i = 1; i <= n; ++i)if (!vis[sta[i]]) { ++col; dfs2(sta[i], col); }//二次 dfsfor (int i = 1; i <= cnt_EEdge; i += 2){int u = EEdge[i ^ 1].to, v = EEdge[i].to;if (Color[u] != Color[v]) { add_Edge(Color[u], Color[v], 1); cnt[Color[v]]++; }//建立新图}for (int i = 1; i <= n; ++i) a[Color[i]] += val[i];//处理新图的点权Topsort(); printf("%d\n", ans);return 0;
}

接下来,在确定理解上述代码之后,讲解 Kosaraju 算法的特性:

  • 对于缩点之后的图上的两个点 i,ji,ji,j,如果 Colori<ColorjColor_i < Color_jColori​<Colorj​,则 iii 一定能够走到 jjj。换言之,求出这张图的拓扑序后 iii 在 jjj 前面。

为什么会有这样的特性呢?简要证明如下:

首先考虑 3 个点的情况:

设上图中的 A,B,C 是 3 个强连通分量。

考虑我们第一遍 dfs 时候栈内 A,B,C 的顺序。

假设首先从 A 开始 dfs,那么 A→B→CA \to B \to CA→B→C,C 进栈,B 进栈,A 进栈,栈内为 CBA,翻转后为 ABC,取出来做第二遍 dfs 时 ColorA<ColorB<ColorCColor_A < Color_B < Color_CColorA​<ColorB​<ColorC​,结论成立。

如果从 B 开始 dfs,那么 B→CB \to CB→C,C 进栈,B 进栈,然后再次从 A 开始 dfs,A 进栈,栈内同样为 CBA,翻转后同样为 ABC。

此时你会发现上面这段话说明了两件事情:

  • 结论的正确性。
  • 结论对于任意一种 dfs 的先后顺序都成立。

综合这两点,可以得知在只有 3 个强连通分量的时候结论完全正确。

那么现在推广到一般情况,对于我们缩点后建出的图而言,是一张 DAG,而这张 DAG 上有若干条链,对于每一条链上的所有强连通分量而言,我们可以类比上述 3 个点的证明得知:无论以何种顺序遍历这条链,翻转栈内这些强连通分量的相对顺序始终是按照链上这些强连通分量的相对顺序排序的。

由于 DAG 就是由这些链共节点组成的,对于每条链都满足上述性质,自然整张 DAG 都满足上述性质。

综上,要证明的结论成立。

2 - SAT 问题求一组可行解时需要用到上述结论。

对 2 - SAT 问题感兴趣的可以看一下我的这篇文章:图论专题-学习笔记:2 - SAT 问题

5. 总结

强连通分量定义:对于一张图 GGG,设其点集为 VVV,如果能够从中选出一些点构成点集 V′V'V′,满足 V′V'V′ 内的所有点都在一个分量内,且 V′V'V′ 内的所有点都不与 V′V'V′ 之外的点在一个分量内,那么点 V′V'V′ 称作图 GGG 的一个强连通分量。

求法:Kosaraju 算法,采用两遍 dfs,第一遍正向图,第二遍将栈反转后反向图。

应用:缩点与 2-SAT。

图论专题-学习笔记:强连通分量相关推荐

  1. 图论专题-学习笔记:虚树

    图论专题-学习笔记:虚树 1. 前言 2. 详解 2.1 虚树定义 2.2 虚树构造 2.3 例题 3. 总结 4. 参考资料 1. 前言 虚树,主要是用于一类树上问题,这类问题通常是需要取一些关键点 ...

  2. 莫比乌斯反演专题学习笔记

    莫比乌斯反演专题学习笔记 本文记录一些和莫反有关的内容的笔记 可能存在诸多谬误,阅读时请谨慎分析 若发现文中有谬误,如您愿意,恳请您向我指出,不胜感激! 为什么要学莫比乌斯反演? 解决一类与狄利克雷卷 ...

  3. 数学/数论专题-学习笔记:狄利克雷卷积

    数学/数论专题-学习笔记:狄利克雷卷积 1. 前言 2. 一些基础函数 3. 积性函数 4. 狄利克雷卷积 5. 总结 6. 参考资料 1. 前言 狄利克雷卷积,是学习与继续探究 μ\muμ 函数和 ...

  4. 数据结构专题-学习笔记:李超线段树

    数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...

  5. 图论学习-有向图强连通分量

    文章目录 有向图强连通分量 1.定义: 2.基本术语与概念 2.1 边的概念 2.2 缩点 2.3 时间戳 3. tarjan求强连通分量(SCC) 3.1 原理 3.2 步骤 3.3 模板 3.3. ...

  6. 算法学习:强连通分量 --tarjan

    [定义] [强连通分量] 在一个子图中,任意点能够直接或者间接到达这个子图中的任意点,这个子图被称为强连通分量 [解决问题] 求图的强连通分量 同时能够起到 ...................缩点 ...

  7. 图论:连通分量和强连通分量

    1.连通图 1.1 顶点的连通性 在无向图G中,若从顶点vi到顶点vj有路径(当然从vj到vi也一定有路径),则称vi和vj是连通的. 1.2 连通图 在无向图G中,若V(G)中任意两个不同的顶点vi ...

  8. 杭电ACM-LCY算法进阶培训班-专题训练(强连通分量)

    点对统计 最大点权 点对统计 Problem Description 给定一个有向图,统计有多少点对u,v(1≤u<v≤n)u,v(1≤u<v≤n)u,v(1≤u<v≤n)满足uuu ...

  9. 【图论】—— 有向图的强连通分量

    给定有向图 ,若存在 ,满足从 出发能到达 中所有的点,则称 是一个"流图"( Flow Graph ),记为  ,其中, 称为流图的源点. 在一个流图  上从  进行深度优先遍历 ...

最新文章

  1. 无需激活直接同步登入discuz,php代码(直接可用)
  2. python安装成功第三方库但import出问题_为什么会在pyspark在RDD中调用python第三方库失败?...
  3. opencv矩阵掩膜操作(提高图片对比度)
  4. PGM:图模型学习概述
  5. pip 源使用阿里云镜像加速
  6. html自动留言,html 留言板
  7. HTML+CSS制作仿制当当网
  8. 51单片机学习笔记0 -- 仿真软件安装(Protues8.0)
  9. 你知道ISO27000信息安全管理标准族有多少?
  10. 跨步电压 matlab仿真,跨步电压,跨步电压触电,跨步电压法,论文集锦_发表网
  11. 代理模式代码举例(java语言版)
  12. 软件全屏使用时点击鼠标自动跳回桌面的问题
  13. 从数学上推导伴随矩阵特征值
  14. 获取当前日期是今年的第几周
  15. ggplot2中1单位线条和字体的究竟相当于多少pt?
  16. scp 远程拷贝文件
  17. 文末送书 | 李航老师新作!机器学习经典著作《统计学习方法》全新升级
  18. Anroid11有多个Launcher应用时,默认其中一个Launcher为启动Launcher,不用弹框选择Launcher
  19. 发布DCWriter电子病历文本编辑器
  20. Ubuntu16.04 编译安装intel SGX Driver,SDK,PSW并验证环境201905

热门文章

  1. PS(MPG)文件格式分析
  2. Dataset - COCO Dataset 数据特点
  3. 电大计算无纸化测试软件,电大无纸化测验ExceL操作步骤.doc
  4. LD_LIBRARY_PATH详解
  5. 当中本聪玩说唱 完爆美国央行之父!
  6. 小蚁智能摄像机服务器故障,小蚁智能摄像机连接Wifi失败密码错误该怎么办?...
  7. 时尚巨头确认遭遇勒索攻击、1100万部手机已感染木马|1月19日全球网络安全热点
  8. java+ssm基于微信小程序的校内商铺购物商城系统 uniapp 小程序
  9. 阿里开发10年大牛:Android开发人员不得不收集的代码(持续更新中)
  10. 大学计算机导论模板,大学计算机导论论文3500字_大学计算机导论毕业论文范文模板.doc...