位于NOI考纲提高组的【7】级算法
【7】求强联通分量算法
【7】强连通分量的缩点算法
【7】求割点、割边算法

认识Tarjan算法

一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法。


001/ 强连通分量

Tarjan算法的最普遍功能

一些前置知识

在有向图中:
连通:两个点互相可达
强连通图:图中任意两点互相可达
强连通分量:有向图的极大连通子图(子图中任意两点互相可达,且不能再添加点进入),也就是以全部强连通的点构成的子图
上图中有两个强连通分量,分别是(1, 2, 3, 7)和(5, 6)。

看个例题

消息的传递

问题描述

时间:三国时期 ;
地点:许昌;人物:曹操,你。
事件:
起因:曹操得知许昌城里有n(n )个袁绍的奸细。(他们编号为1到n,奸细间存在着一 种消息传递关系,即若C[i][j]=1,表示奸细i能直接把消息传给奸细j)。
经过:曹操想发布一个假消息,需要传达给所有奸细。曹操命令你来负责消息的发布。
结果:聪明的你把消息传递给了很少的几个奸细,就使所有奸细都得到了这个消息。
问:最少传递给几个奸细就能完成任务?

输入格式

第一行为N,第二行至第N+1行为N*N的矩阵(若第I行第J列为1,则奸细I能将消息直接传递给奸细J,若第I行第J列为0,则奸细I不能直接将消息传给J )

输出格式

你最少要传递的奸细的个数

样例输入

8
0 0 1 0 0 0 0 0
1 0 0 1 0 0 0 0
0 1 0 1 1 0 0 0
0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 1 0 0 0 1
0 0 0 0 0 0 1 0

样例输出

2

在这道题中,我们将消息传递关系建图,边 (i,j)\left(i, j\right)(i,j) 表示 iii 能给 jjj传递消息。由于强连通分量内节点互相可达,所以在同一强连通分量中的“奸细”中只需要选择一个就可以使整个子图内的“奸细”都得知消息。

将每个强连通分量都缩成一个节点,再重新进行建边。此时建出的图是一个有向无环图。最后只需要统计入度为0的节点数量就是最终的答案,因为只要一个点有入度,它就一定可以由其他节点到达。


此题中的查找强连通分量然后缩点就是此类题型的基本做法。

接下来讨论核心算法。

求强连通分量的两个方法

  1. Kosaraju算法

    Kosaraju算法的核心在于首先将图反转进行dfs,取强连通分量的后序排列。

    回到上图,当使用dfs寻找强连通分量时,如果一开始从1开始dfs,则当遍历到2时路径形成了一个分叉,为了确认该强连通分量,我们希望2不走到5节点上去,而是继续到3,然后到7,最后形成完美的闭环。

那么正向的dfs该如何做到规避掉这样的路径分叉呢?

由于反转图和正向图的强连通分量情况相同,我们将图反转,dfs求出每一个节点的后序编号。此时从任何地方开始遍历,强连通分量(1, 2, 3, 7)都将处于序列的末尾。

(反转图)

此时再按照这个后序编号进行正向图的dfs,就可以确保强连通分量(5, 6)在(1, 2, 3, 7)之前被标记到,以保证搜索到2或3时会跳转到5节点。

//消息的传递  Kosaraju算法
#include <bits/stdc++.h>
using namespace std;
long  long n, m, pos, scc, lst[1007], vis[1007], d[1007];
bool a[1007][1007];
long  long dfs (long  long p)  { //查找反图vis[p]  =  1;for (long long i = 1; i <= n; i ++)if (a[p][i] && !vis[i]) dfs (i);lst[++ pos] = p;
}
long long dfs1 (long long p)  { //正向查找vis[p]  = scc;for (long long i = 1; i <= n; i ++)if (a[i][p] && !vis[i]) dfs1 (i);
}
int main () {scanf ("%lld", &n);for (long  long i =  1; i <= n; i ++)  {for (long  long j = 1; j <= n; j ++)  {scanf ("%d", &a[i][j]);}}for (long long i = 1; i <= n; i ++)  {vis[i] = false;}for (long long i = 1; i <= n; i ++)  {if (!vis[i]) dfs(i);}for (long long i = 1; i <= n; i ++)  {vis[i] = false;}scc = 1;for (long long i = n; i >= 1; i --)  {if (!vis[lst[i]])  {dfs1 (lst[i]);scc ++;}}//缩点for (int i = 1; i <= n; i ++)  {for (int j = 1; j <= n; j ++)  {if ((vis[i] != vis[j]) && (a[i][j])) d[vis[j] - 1] = 1;}}int tot = 0;//统计答案for (int i = 1; i <= scc - 1; i ++)  {if (!d[i]) tot ++;}printf ("%lld", tot);return  0;
}
  1. Tarjan算法

    核心在于利用搜索树查找强连通分量。

    dfs时,我们通过一个节点往它所连接的节点搜索,因此将搜索的路径画出来就是一棵树,且父亲节点指向子孙节点,这样的树一定是原图的一个子图。当然还会有多出来的边。不难发现,由于搜索树是从上到下的有向无环图,所以强连通分量(环)也就只会出现在多出来的从子孙指向其祖先的边上。从该祖先到该子孙的路径就是这个强连通分量。

    在进行dfs的过程中,记录两个信息:

    dfn[u] 表示被搜索到的次序

    low[u] 表示u能够到达的节点中最小的dfn[v]

    在搜索过程中,从uv的节点有三种情况:

    1. v未被访问过:搜索v,然后用low[v]更新low[u]

    2. v已经被访问过,还在栈中:说明v还没有更新完它的强连通分量,用dfn[v]更新low[u]

    3. v已经被访问过,但不在栈中:说明v已经完成它的使命,被栈弹出不会回溯到了,因此不做任何操作。

    另:在搜索过程中,将搜索栈和节点是否进栈用instack[] 数组表示出来,更方便查询。

    在一个强连通分量中,有且仅有一个节点u满足dfn[u] == low[u],它是连通分量中最小,也是第一个被查询到的点。判断该节点,则栈中它之上的节点都是它的连通分量中的节点,对它们进行统一标记并编号。


```cpp
//消息的传递  Tarjan算法
#include  <bits/stdc++.h>
using  namespace std;
int scc, n, a[1007][1007], d[1007], id[1007];
int dfn[1007], low[1007], vis[1007], instack[1007], cnt;
stack <int> s;
void Tarjan (int u)  {dfn[u]  = low[u]  =  ++cnt;s.push (u);vis[u]  =  1;instack[u]  =  1;for  (int v =  1; v <= n; v ++)  {if  (a[u][v])  {if  (!vis[v])  { //v未被访问过Tarjan (v);low[u]  = min (low[u], low[v]);}else  if  (instack[v])  { //v被访问过,且还在栈中low[u]  = min (low[u], dfn[v]);}}}//缩点if  (dfn[u]  == low[u])  {scc ++;int v;do  {v = s.top ();s.pop ();instack[v]  =  0;id[v]  = scc;}  while  (u != v); //u是该连通分量第一个进栈的节点}
}
int main () {scanf ("%d", &n);for (long long i = 1; i <= n; i ++)for (long long j = 1; j <= n; j ++)scanf ("%d", &a[i][j]);int ans = 0;for (int i = 1; i <= n; i ++)if (!vis[i])Tarjan (i);for (int i = 1; i <= n; i ++)for (int j = 1; j <= n; j ++)if  (id[i] != id[j]  && a[i][j])d[id[j]] = 1;for (int i = 1; i <= scc; i ++)  if (!d[i]) ans ++;printf ("%d", ans);return  0;
}

002/ 双连通分量

在无向图中:
割点:删除该点后这个连通子图变得不再连通。换句话说,删掉这个点这个图的极大连通分量数增加,这个点就叫割点。
割边(桥):同理,删除割边则图的极大连通分量数会增加。
在连通的无向图中:
边双连通分量:无论删去哪条边都不能使该分量不连通,也就意味着没有割边
点双连通分量:无论删去哪个点都不能使该分量不连通,意味着没有割点

  1. 运用Tarjan求割点


在这一张图中,3是割点。

我们画出树边,然后写出每个节点的dfn和low

当一个节点u存在任何一个儿子节点v使得dfn[u]<=low[v],也就说明v不能通过任何一条路径回溯到比u更靠上的节点,所以去掉u就会把v及v的子树断开,u是割点。

但这时有一个特殊情况,就是节点u为生成树的根节点。这时只要u有两个以上的儿子,u就一定是割点。因为这时的子节点无法回溯到u更上的节点,无向图也没有横向连接两棵树的“横叉边”。

运用到Tarjan算法当中,只需要在原来的基础上,在连接树边之后判断即可。


```cpp
for  (int v =  1; v <= n; v ++)  {if  (a[u][v])  {if  (!vis[v])  { //v未被访问过Tarjan (v);low[u]  = min (low[u], low[v]);if  (low[v] >= dfn[u]) {//也可以在此处记录割点uans ++; //统计割点}else  if  (instack[v])  { //v被访问过,且还在栈中low[u]  = min (low[u], dfn[v]);}}
}

最后一定要增加一个关于特殊情况的判断,可以通过统计根节点儿子的数量完成,这里就懒得放了。

  1. 运用Tarjan求割边(桥)
    只需要更改一处:low[v] > dfn[u] 就可以了。不需要判断根节点。
    不放代码了。

003/ 2-SAT

一种将逻辑限制转换为图论的思想

整个例题:

聚会

问题描述

有n对夫妻(编号0到n-1)被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?

输入格式

多组数据
n: 表示有n对夫妻被邀请 (n<= 1000)
m: 表示有m 对矛盾关系 ( m < (n - 1) * (n -1))
在接下来的m行中,每行会有4个数字,分别是 A1,A2,C1,C2
A1,A2分别表示是夫妻的编号
C1,C2 表示是妻子还是丈夫 ,0表示妻子 ,1是丈夫(C1跟C2有矛盾)
夫妻编号从 0 到 n -1

输出格式

对于每组数据:
如果存在一种n个人同时列席的情况 则输出YES
否则输出 NO

样例输入

2
1
0 1 1 1

样例输出

YES

在这道题里,每一对夫妻都有两个状态(丈夫/妻子去),而对于这许多对夫妻存在一些矛盾关系。寻找同时列席的情况的过程就可以称作 2-SAT 问题。

每一对夫妻看作一个两个元素的集合,集合aaa的两个元素分别就为aaa和¬a\neg a¬a,aaa表示男的去,¬a\neg a¬a表示女的去。

一个矛盾关系如第aaa个丈夫和第bbb个妻子有矛盾,就表示为⟨a,¬b⟩\left\langle a, \neg b \right \rangle⟨a,¬b⟩

可以用位运算的方式表示出来,即a&¬b=falsea \& \neg b = falsea&¬b=false

那么这样的式子如何转化到图论中呢?

还是刚才的那个矛盾关系,其中如果第aaa个丈夫aaa去了,那么第bbb对夫妻就只有让丈夫bbb去。同理,如果第bbb个妻子¬b\neg b¬b去了,则第aaa对夫妻就只有让妻子非¬a\neg a¬a去。

将这样的关系连边,即连(a,b)\left( a, b \right)(a,b)和(¬a,¬b)\left( \neg a, \neg b \right)(¬a,¬b)

然后使用Tarjan缩点,处在同一强连通分量里的节点就是必须同时列席的。所以,如果一对夫妻aaa和¬a\neg a¬a同时出现在一个强连通分量里面,则被判断为无解。

这就是2-SAT问题的基本解决方式。

注意摸清“如果选择集合aaa中的uuu,则集合bbb必须选择vvv。连边 (u,v)\left( u, v \right)(u,v)

代码:

#include  <bits/stdc++.h>
using  namespace std;
int n, m;
vector <int> mp[2007];
void add (int u, int v) {mp[u].push_back (v);
}
int dfn[2007], low[2007], cnt, scc, id[2007];
bool instack[2007];
stack <int> s;
void Tarjan (int u) {dfn[u] = low[u] = ++cnt;instack[u] = 1;s.push (u);int len = mp[u].size ();for (int i = 0; i < len; i ++) {int v = mp[u][i];if (!dfn[v])  {Tarjan (v);low[u] = min (low[u], low[v]);}else if (instack[v]) {low[u] = min (low[u], dfn[v]);}}if (dfn[u] == low[u])  {scc ++;int v;do {v = s.top ();s.pop ();instack[v] = 0;id[v] = scc;} while (v != u);}
}
void init () {for (int i = 0; i <= n * 2; i ++) mp[i].clear ();memset (dfn, 0, sizeof dfn);memset (low, 0,  sizeof low);memset (id, 0, sizeof id);memset (instack, 0, sizeof instack);while (!s.empty ()) s.pop ();cnt = scc =  0;
}
bool check () {for (int i = 0; i < n; i ++) {if (id[i * 2] == id[i * 2 + 1]) {return 0;}}return 1;
}
int main () {while (scanf ("%d %d", &n, &m) == 2) {init ();for (int i = 1; i <= m; i ++) {int a1, a2, c1, c2;scanf ("%d %d %d %d", &a1, &a2, &c1, &c2);int u = a1 * 2 + c1;int v = a2 * 2 + c2;add (u, v ^ 1);add (v, u ^ 1);}for (int i =  0; i <= n * 2 - 1; i ++) {if (!dfn[i]) {Tarjan (i);}}if (check()) printf ("YES\n");else printf ("NO\n");}return  0;
}

End.

Tarjan算法及其引申相关推荐

  1. 0x66.图论 - Tarjan算法与无向图连通性

    目录 一.无向图的割点与桥 割点 桥/割边 时间戳 搜索树 追溯值 二.割边判定法则 三.割点判定法则 1.luogu P3388 [模板]割点(割顶) 2.luogu P3469 [POI2008] ...

  2. tarjan算法不是很懂先mark一下。

     前面为转载的.后面是自己的理解. 三种tarjan算法(上) .这篇算是做一个总结吧. 求强连通分量 求无向图的割和桥 最近公共祖先 求强连通分量 基本概念:       强连通是有向图才有的概念. ...

  3. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)...

    转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2194090a96bbed2db1351de8.html 基本概念: 1.割点:若删掉某点后,原连通图 ...

  4. 『Tarjan算法 无向图的双联通分量』

    无向图的双连通分量 定义:若一张无向连通图不存在割点,则称它为"点双连通图".若一张无向连通图不存在割边,则称它为"边双连通图". 无向图图的极大点双连通子图被 ...

  5. Tarjan算法学习笔记

    一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法. [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected ...

  6. 『追捕盗贼 Tarjan算法』

    追捕盗贼(COCI2007) Description 为了帮助警察抓住在逃的罪犯,你发明了一个新的计算机系统.警察控制的区域有N个城市,城市之间有E条双向边连接,城市编号为1到N. 警察经常想在罪犯从 ...

  7. 算法提高课-图论-有向图的强连通分量-AcWing 367. 学校网络:强连通分量、tarjan算法

    文章目录 题目解答 题目来源 题目解答 来源:acwing 分析: 第一问:通过tarjan算法求出强连通分量并且缩点后,统计入度为0的点的个数p即可. 第二问,至少加几条边才能使图变成强连通分量?这 ...

  8. CSP认证201509-4 高速公路[C++题解]:强连通分量、tarjan算法模板题

    题目分析 来源:acwing 分析: 所求即为强连通分量的个数,然后计算每个强连通分量中点的个数,相加即可. 所谓强连通分量,它是一个子图,其中任意两点可以相互到达,并且再加一个点,就不能满足任意两点 ...

  9. 算法提高课-图论-有向图的强连通分量-AcWing 1174. 受欢迎的牛:tarjan算法求强连通分量、tarjan算法板子、强连通图

    文章目录 题目解答 题目来源 题目解答 来源:acwing 分析: 强连通图:给定一张有向图.若对于图中任意两个结点x,y,既存在从x到y的路径,也存在从y到x的路径,则称该有向图是"强连通 ...

最新文章

  1. 用户体验改善案例_如何检测用户的设备,以便改善他们的用户体验
  2. 百度小程序opencard书法字典名家书法等测试日志记录
  3. Zookeeper集群搭建方法
  4. 如何关闭mac烦人的更新升级提醒
  5. 【C/C++】顺序容器list和vector
  6. 以太坊 事务处理流程
  7. **产品经理之流程图**
  8. c语言 多核优化,【模型工具】一种对SWMM5的多核优化
  9. 信噪比计算方式(小问题解惑)
  10. iOS 10 通知 --UserNotifications
  11. (原)red-green Image.合成三维立体效果, 红绿眼镜 3D图 ,三维图片的核心算法。googler.cc上面有完善的程序和源码下载。 网上以前的那个有错误,我纠正了下~!~
  12. 音乐学院计算机音乐实验中心,中央音乐学院鼎石实验学校
  13. java1.8新特性之stream流式算法
  14. 一键adb连接网易Mumu模拟器
  15. 2022临床助理医师考试专业知识模拟题
  16. aws mysql 多区_AWS RDS多可用区+EC2实例跑mysql从库的测试
  17. mysql中工资表,( 13 ) 数据库中有工资表 , 包括 “ 姓名 ” 、 “ 工资 ” 和 “ 职称 ” 等字段 , 现要对不同职称的职 - 赏学吧...
  18. pytorch中repeat方法
  19. Mac Pro install peel
  20. 一篇解决:Ubuntu安装配置、软件、工具、快捷键

热门文章

  1. java 魔鬼数字_什么是代码中的魔鬼数字,如何解决?
  2. SURF SIFT ORB三种特征检测算法比较
  3. 杂学第十篇:这几天,在毕业论文调整格式中踩过的坑,满满的干货助你快速解决格式调整的烦恼
  4. 当代大学生的解压方式:WinRAR
  5. 别找了,你要的财务预算表都在Smartbi
  6. Android logd日志原理
  7. python破解wifi易懂源代码
  8. wordpress 古腾堡_超越古腾堡
  9. 一款支持HomeKit的摄像头?智汀 IPC摄像头IC1开箱体验
  10. 定时器及其应用【配置寄存器】