有向图的强连通分量。
例题
题目链接: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:求每个点数不为一的强连通分支里面的最小点数
其他题目:
HDU 1269 判断一个图是否是强连通的
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1269
https://vjudge.net/problem/HDU-1269
HDU 3072 强连通后缩点,DAG的最小树形图
链接:http://acm.hdu.edu.cn/showproblem.php?pid=3072
https://vjudge.net/problem/HDU-3072
POJ 2186 求多少点可以被其他点到达(明星奶牛)
链接:http://poj.org/problem?id=2186
https://vjudge.net/problem/POJ-2186
HDU 5934 缩点后找入读为0的强连通块中最小的值累加
链接:http://acm.hdu.edu.cn/showproblem.php?pid=5934
https://vjudge.net/problem/HDU-5934
HDU 2767 求最少加几条边才能使得有向图变成强联通,先强联通缩点,然后求出DAG,入度为0的点的个数是a个,入度为b的点是b个,那么答案就是max(a,b)
链接:http://acm.hdu.edu.cn/showproblem.php?pid=2767
https://vjudge.net/problem/HDU-2767
UVA 11324 the largest clique 缩点以后变成DAG,发现每个强连通分量要么都选,要么都不选,那么在dag里面,求弱连通的最大点数可以用dp解决
链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2299
https://vjudge.net/problem/UVA-11324
POJ 3592
链接:http://poj.org/problem?id=3592
https://vjudge.net/problem/POJ-3592
NOIP2009提高 最优贸易
链接:
LOJ:https://loj.ac/problem/2590
Luogu:https://www.luogu.com.cn/problem/P1073
其他OJ不给了。
POJ 2553 The Bottom of a Graph(alpc OJ 1274)(基础)
链接:http://poj.org/problem?id=2553
https://vjudge.net/problem/POJ-2553
POJ 1236 Network of Schools (基础)
链接:http://poj.org/problem?id=1236
https://vjudge.net/problem/POJ-1236
POJ 2762 Going from u to v or from v to u? (中等,弱连通分量 )
链接:http://poj.org/problem?id=2762
https://vjudge.net/problem/POJ-2762
POJ 3160 Father Christmas flymouse(难,DP题)
链接:http://poj.org/problem?id=3160
https://vjudge.net/problem/POJ-3160
POJ 1904 King‘s Quest (难,推荐,非缩点,匹配思想与强连通分量的转化)
链接:http://poj.org/problem?id=1904
https://vjudge.net/problem/POJ-1904