强连通分量

  • 先来一题例题
    • 题目大意
  • 怎么做?
    • 分析
    • 结论
    • 不要高兴得太早
  • 怎么办呢?
    • 定义
    • 缩点法
    • 原图构建新图
    • 发现
    • 新的结论
  • 强连通分量算法
    • Kosaraju算法
    • Tarjan算法
  • 例题:信息传递
    • 分析
    • 代码
  • 其他题目

有向图的强连通分量。

先来一题例题

题目链接:http://poj.org/problem?id=2186
vjudge:https://vjudge.net/problem/POJ-2186

题目大意

有一群牛,总数为N(N<=10000)。
题目数据为牛之间的关系,比如说1仰慕2,2仰慕3等等,设这种仰慕是可以传递的,如果1仰慕2,那么1也会同时仰慕2仰慕的那些牛。(关系数e<=50000)
如果一头牛被所有的牛都仰慕,那么它将是最受欢迎的牛。
问有多少牛是"最受欢迎的"。

样例数据:

本例数据中最受欢迎的牛为D牛,E牛。

怎么做?

显然,采用floyd传递闭包方法简单模拟可以解决此问题。时间复杂度o(n^3)。
枚举所有点,反向dfs深度优先遍历图。用邻接表存储边,每次搜索时间复杂度o(n+e),总复杂度o(n*(n+e))。
但是由于数据量巨大(n=10000) ,这两种做法超时的可能性很大。
那么有没有更好的做法呢?

分析

首先让我们考虑问题的简化版本——有向图没有环路的情况,也就是说:
不存在一条仰慕路线,可以从一头牛出发,再回到该牛。即不存在牛互相仰慕的情况。
如果有最受欢迎的牛存在,为保证该牛被所有的牛都仰慕,这个有向图将是连通图,最受欢迎的牛不会仰慕其他牛,即他的出度为零。
最受欢迎的牛被其他所有牛仰慕,即出度为零的点应有且仅有一个。

结论

这让我们就将简化版问题给解决了~
首先,统计出图中出度为0的点。
如果这样的点只有一个,那么从该点出发反向dfs遍历图。如果可以遍历所有点,那么说明这个点就是题目要求的点。
时间复杂度o(n+e)。

不要高兴得太早

上面的结论在有环图是否适用呢?

很可惜,结论不成立。
因为大牛可以互相仰慕,所以若最受欢迎的牛与其他牛互相仰慕,这些牛都是最受欢迎的牛。这样,满足条件的点出度可能不为0,也不止有一个。

怎么办呢?

现在,强连通分量算法出场的时候到了。

定义

下面给出强连通分量的定义:
有向图中, u可达v不一定意味着v可达u. 相互可达则属于同一个强连通分量
Strongly Connected Component,简称SCC

根据定义,在上题中,一组互相仰慕的牛就构成了一个强连通分量(一个大牛)。

缩点法

求出强连通分量有什么用呢?
我们可以将每个强连通分量看作一个内外隔绝的包裹,忽略包裹内部的冗余边,并将这个包裹同外部点的相连的边保留,将其打包压缩成一个新的点存储下来 。
最后再利用这些新的点重新建图 。
这就是缩点法。

原图构建新图


强连通分量A,B,C压缩成点S1
强连通分量F,G压缩成点S2
强连通分量D,E压缩成点S3

发现

新图与原图有什么不同?
因为强连通分量全部被压缩成点,新图将一定是一张有向无环图。
而对有向无环图的问题结论我们之前已经得出了。

新的结论

很容易得到新的结论:
利用强连通分量的缩点法构建新图,在新图中如果只有一个出度为0的点且该点与其他所有点连通,那么这个强连通分量中的所有点就是题目要求的点。

下面的问题,就是如何求强连通分量了。

强连通分量算法

求强连通分量有三种算法,分别是Kosaraju算法,Gabow算法和Tarjan算法,时间复杂度均为o(n+e)。
其中Kosaraju算法要对原图和逆图都进行一次DFS,另外两种算法只要DFS一次, Gabow算法是Tarjan算法的改进。

下面简单介绍Kosaraju算法。

Kosaraju算法


我们继续借助DFS,如上图所示,如果从f开始DFS,我们就可以得到包含f和g的一棵DFS树,然后从c出发,得到c和d和h,再从a出发,得到a和e和b,这样我们每次就可以得到一个SCC,如上图所示.

遗憾的是,并不是每个顺序都是“好用”的,如果一开始就不幸选择了从a开始进行遍历,这颗DFS树将包含整个图,也就是说,这次不明智的DFS把所有SCC混在了一起,什么也得到,很明显,我们希望按照SCC图拓扑顺序的逆序进行遍历,这样才能每次DFS得到一个SCC,而不会把两个或者多个SCC混在一起。

于是,我们先对原图进行一次DFS,记录每个点结束递归(出栈)的时间,然后再按照出栈的顺序从晚到早依次对原图的逆图进行DFS,这样每次得到的连通块就是一个强连通分量。

算法步骤:

  • 对有向图进行DFS,记录下顶点变黑的时间A[i]。
  • 改变图G 的每一条边的方向,生成新图GT。
  • 按上次DFS 顶点变黑的时间A[i]由大到小顺序对GT进行DFS。遍历结果构成森林W 。
  • W 中每棵树的结点构成了有向图的一个强连通分量。

DFS:

  • 在演示之前,重新介绍DFS深度优先搜索为结点着色以表示结点的状态的过程。
  • 每个顶点开始均为白色。
  • 搜索中被发现时置为灰色。
  • 结束时又被置成黑色(即当其邻接表被完全检索之后)。

演示:
对G进行dfs,记录下顶点变黑时间A[i]。

得到A[i]从大到小的顺序:
F G A C D E B
改变图G 的每一条边的方向,生成新图GT
按A[i]由大到小顺序F G A C D E B对GT进行第二次DFS
得到森林w
FG||ACB||DE

根据森林w
FG||ACB||DE
由每个强连通分量为搜索树中的一棵子树。 得到已拓扑有序的强连通分量
S2||S1||S3


总结:
对某些题目,内部点之间的联系不会和外部点作用而影响到结果,那么强连通分量可以缩成一点,考虑为一个整体。
缩点可以简化构图(有环图变无环图),这样更容易发掘出各个强连通分量外部之间的规律。

void dfs(int k) {low[k]=dfn[k]=++cnt;s.push(k); //k点入栈for(int i=h[k]; i!=-1; i=e[i].nt) {int v=e[i].to;if(!dfn[v]) {dfs(v);low[k]=min(low[k],low[v]);} else if(!belong[v]) low[k]=min(low[k],low[v]);//如果v被标记,但是还不属于任何一个SCC,则v还在栈里}if(low[k]==dfn[n]) {scc++;for(;;) { //不断弹出栈里的点int x=s.top();s.pop();belong[x]=scc; //编号if(x==k) break;}}
}

Tarjan算法

伟大的Tarjan又给我们提供了一种求强连通分支的方法,通过dfn和low数组,轻而易举的解决了这个问题。

任何一个强连通分量,必定是对原图的深度优先搜索树的子树。若强连通分量C中第一个被发现的是x,则C中其他点都是x的后代。我们希望在x访问完成时立刻输出C,这样就可以在同一棵DFS树种区分开所有SCC了,因此问题关键,是判断一个点是否是一个SCC中最先被发现的点。

void dfs(int x) {dfn[x]=low[x]=++t;s[++top]=x;for(int i=h[x]; i!=-1; i=e[i].nt) {int v=e[i].to;if(!dfn[v]) {dfs(v);low[x]=min(low[x],low[v]);} else if(!bl[v]) low[x]=min(low[x],dfn[v]);}if(low[x]==dfn[x]) {scc++;while(1) {int v=s[top--];bl[v]=scc;sz[scc]++;if(v==x) break;}}
}

例题全代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>#define R                  register int
#define re(i,a,b)          for(R i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))using namespace std;typedef long long ll;int const N=10005;
int const M=50005;struct Edge {int to,nt;
} e[M<<1];int cnt,n,m,t,scc,top;
int h[N],bl[N],sz[N],dfn[N],d[N],s[N],low[N];inline void add(int a,int b) {e[cnt].to=b;e[cnt].nt=h[a];h[a]=cnt++;
}void dfs(int x) {dfn[x]=low[x]=++t;s[++top]=x;for(int i=h[x]; i!=-1; i=e[i].nt) {int v=e[i].to;if(!dfn[v]) {dfs(v);low[x]=min(low[x],low[v]);} else if(!bl[v]) low[x]=min(low[x],dfn[v]);}if(low[x]==dfn[x]) {scc++;while(1) {int v=s[top--];bl[v]=scc;sz[scc]++;if(v==x) break;}}
}int main() {scanf("%d%d",&n,&m);ms(-1,h);while(m--) {int x,y;scanf("%d%d",&x,&y);add(x,y);}for(int i=1; i<=n; i++) if(!dfn[i]) dfs(i);for(int i=1; i<=n; i++) for(int j=h[i]; j!=-1; j=e[j].nt) {int x=i,y=e[j].to;if(bl[x]==bl[y]) continue;d[bl[x]]++;}int t=n+1;for(int i=1; i<=scc; i++) if(d[i]==0) {if(t==n+1) t=i;else t=0;}cout << sz[t] << endl;return 0;
}

例题:信息传递

「NOIP2015提高」信息传递
题目链接:
LOJ:https://loj.ac/problem/2421
UOJ:http://uoj.ac/problem/146
Luogu:https://www.luogu.com.cn/problem/P2661
计蒜客:https://nanti.jisuanke.com/t/T2026
51Nod:https://www.51nod.com/Challenge/Problem.html#problemId=2849
vjudge:https://vjudge.net/problem/LibreOJ-2421

分析

把整个传递的过程看成是一个有向图,每个点最多只有一条出边,每个点可能有多条入边,缩点以后,我们发现答案就在每个强连通分支里面,又由于边的性质(每个点最多只有一条出边,可能有多条入边),那么每个强连通分支一定是一个简单的环(没有环套环).

方法1:可以直接o(n)循环找环
方法2:先拓扑排序,然后剩下的都是环
方法3:求每个点数不为一的强连通分支里面的最小点数

代码

代码不贴了,给个直通车:https://loj.ac/submission/809581

其他题目

HDU-1269 判断一个图是否是强连通的
链接:HDU vjudge

HDU-3072 强连通后缩点,DAG的最小树形图
链接:HDU vjudge

POJ-2186 求多少点可以被其他点到达(明星奶牛)
链接:POJ vjudge

HDU-5934 缩点后找入读为0的强连通块中最小的值累加
链接:HDU vjudge

HDU-2767 求最少加几条边才能使得有向图变成强联通,先强联通缩点,然后求出DAG,入度为0的点的个数是a个,入度为b的点是b个,那么答案就是max(a,b)
链接:HDU vjudge

UVA-11324 the largest clique 缩点以后变成DAG,发现每个强连通分量要么都选,要么都不选,那么在dag里面,求弱连通的最大点数可以用dp解决
链接:UVA vjudge

POJ-3592
链接:POJ vjudge

NOIP2009提高-最优贸易
链接:LibreOJ-2590 计蒜客-T2095 vjudge

POJ-2553 The Bottom of a Graph(alpc OJ 1274)(基础)
链接:POJ vjudge

POJ-1236 Network of Schools (基础)
链接:POJ vjudge

POJ-2762 Going from u to v or from v to u? (中等,弱连通分量 )
链接:POJ vjudge

POJ-3160 Father Christmas flymouse(难,DP题)
链接:POJ vjudge

POJ-1904 King‘s Quest (难,推荐,非缩点,匹配思想与强连通分量的转化)
链接:POJ vjudge 题解

【C++】强连通分量相关推荐

  1. 极小连通子图和极大连通子图_强连通分量与拓扑排序

    前言 由于GacUI里面开始多处用上拓扑排序,我决定把之前瞎JB搞出来的算法换掉,换成个正式的.之前我自己弄了个写起来很简单的算法,然后每一处需要用到的地方我就重新做一遍.当然这样肯定也是不行的,我觉 ...

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

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

  3. HDU4635(强连通分量+Kosaraju算法)

    题意:给出一个有向图,最多添加多少条边使这个图依然不是强连通图:当这个图是强连通图时,输出-1: 求解思路:强连通分量求解: 强连通图:在有向图中,任意节点除法都可以到达其余所有节点,则称为强连通图. ...

  4. poj3352(强连通分量)

    题意:添加多少边才能使这个无向图为双连通分量. 注意:双连通分量适用于无向图:而强连通分量适用于有向图.但是这两个概念都是一样的. #include<iostream> #include& ...

  5. poj2553(强连通分量)

    题意:给出有向图,图的底部是所有汇节点的子集,即,底部(G)={v∈V\x-∀w∈V:(v→w)⇒(w→v)},w在--里面G可以从v, v也可从w.让我们求有多少个点是绘点的问题. 思路:先求解出强 ...

  6. poj2186(强连通分量)

    思路:找出出度为0 的顶点,如果出度为0的顶点大于1,则解为零,否则解就是出度为零的顶点的连通分支数. 刚开始是没有理解这道题的,也是看了大神之后才理解的. 方法一: #include<iost ...

  7. poj1236(强连通分量)

    题意: (1).至少需要向多少个学校发放软件,要使这个网络中的所有学校都能得到软件 (2).至少需要添加多少条边,才能使这个网络成为一个强连通分量图. 思路:首先求解强连通分量,如果不是同一个强连通分 ...

  8. HDU5934(强连通分量)

    题意:主要在诈弹爆炸的范围内如果存在其他的诈弹,那么在周围的诈弹也将会被引爆,这样思路就可以想到强连通分量了: 思路:先求解强连通分量,在找到强连通分量时,也求解出这个强连通分量的最小费用mincos ...

  9. HDU2767(强连通分量+Kosaraju算法)

    题意:需要加多少边才能把一个图变成强连通分量 强连通图:在有向图中,任意节点除法都可以到达其余所有节点,则称为强连通图. 强连通分量:在非强连通图的有向图中,选取部分点为强连通图,该强连通子图称为强连 ...

  10. HDU1827(强连通分量)

    强连通图:在有向图中,任意节点除法都可以到达其余所有节点,则称为强连通图. 强连通分量:在非强连通图的有向图中,选取部分点为强连通图,该强连通子图称为强连通分量. 注意:这道题只给出的是Wiskey和 ...

最新文章

  1. 使用leangoo实现多泳道看板任务
  2. CLI、终端 (Terminal)、Shell、TTY、Console、Bash都是什么?
  3. 构造函数未定义_构造函数(constructor)和观察者模式,谁略胜一筹呢?
  4. [译]Chipmunk 教程2 - 基本概念
  5. java.lang.IllegalStateException: Failed to read Class-Path attribute from manifest of jar file:/XXX
  6. mysql日志管理_关于MySQL的日志管理(binlog)
  7. 前端学习(2013)vue之电商管理系统电商系统之监听on-success事件
  8. 生成注释_java基础- Java编程规范与注释
  9. WSE2.0中X509安全令牌的使用
  10. 全国计算机统考在线模拟网站,全国计算机统考模拟试题
  11. [论文翻译]Attention Is All You Need
  12. C#:VARCHART XGantt 5.2.0.167-2022-08-18-UPDATE
  13. 电脑时间不同步怎么办?
  14. java io 系列(转载skywang12345)
  15. ngnix 端口映射
  16. android l root 方法,安卓L怎么Root 新版Android L一键root教程
  17. 中国10大经典徒步线路(资深徒步专家@行摄匆匆推荐)
  18. 3D游戏开发中的矩阵详解
  19. Matlab验证码识别
  20. 计算机主机光盘故障,电脑开机之后提示插入安装光盘的解决方法

热门文章

  1. OLAP的多维分析操作
  2. beego task
  3. php 随机数 抽奖 页面,源生JS做出抽奖页面
  4. 嘉楠科技第一代人工智能芯片勘智Kendryte惊艳亮相
  5. Linux——ld命令
  6. 怎样录制电脑内部发出的声音
  7. linux mtr(my traceroute ping + traceroute)
  8. 查看Linux下文件和文件夹大小_莫枫恋_新浪博客
  9. ELF文件格式概要介绍
  10. C++基础-拷贝构造函数(深拷贝与浅拷贝)