Tarjan算法 有向图SCC

Posted maoyiting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tarjan算法 有向图SCC相关的知识,希望对你有一定的参考价值。

一、引言

强连通分量是指有向图的一个极大联通子图,强连通分量中任意两个点都存在一条路径可以直接或间接互相到达。特别地,有向图G中,若对于 V(G) 中任意两个不同的顶点 u 和 v,都存在从 u 到 v 以及从 v 到 u 的路径,则称 G 是强连通图。

技术图片

有向图的极大强连通子图被称为是“强连通分量”,简记为SCC。

举个栗子。如右图,G1是强连通的,G2不是强连通的。因为G2中,点4不能到达点1。

求强连通分量的Tarjan算法,时间复杂度可以做到 O(n+m)。

二、强连通分量缩点

若将有向图中的强连通分量都缩成一个点,则原图会形成一个DAG(有向无环图)。举个栗子:

技术图片

三、基本算法

一些定义

Tarjan算法是基于深搜的算法,每个强联通分量为搜索树种的一棵子树。搜索时,把当前搜索树中未处理的结点加入堆栈,回溯时就可以判断栈顶到栈中的结点是否为一个强联通分量。

为了描述方便,我们对图的边进行一些定义。一棵 DFS 树被构造出来后,考虑图中的非树边,对边定义如下:技术图片

前向边:祖先→儿子的边。

后向边:儿子→祖先的边。

横叉边:没有祖先、儿子关系的边(注意横叉边只会往 dfn 减小的方向链接。)

dfn[x]:记录结点x在DFS过程中被遍历到的次序号(时间戳)。可知在同一个DFS树的子树中,dfn[x]越小,则其越浅。

low[x]:记录结点x或x的子树能够追溯到的dfn最小的值(栈中标号的最小点)。即在DFS树中,此点以及其后代指出去的边,能返回到的最浅的点的时间戳。

low的计算过程

我们阔以模拟一下low的计算过程。

举个栗子。

dfn[x]为结点x在DFS过程中被遍历到的次序号,不用解释。low[x]为结点x或x的子树能够追溯到的dfn最小的值(在DFS树中,此点以及其后代指出去的边,能返回到的最浅的点的时间戳)。

从结点1开始遍历。当到搜索结点5的时候,结点5已经没有指出去的边了,就是说,它不能再指向更浅的时间戳了。它就指向自己,所以结点5的low值就是4。接下来搜索4号点、6号点。搜索到6号点后,6号点返回,找到了4号点。所以6号点能搜索到的最浅的时间戳就是5(即4号点的dfn值)。4号点不能往其他地方遍历了,所以它的low值就是5。同理,可推出其他结点的low值。

技术图片

可得,当dfn[x]=low[x]时,以x为根的搜索子树上所有结点构成一个强联通分量。

证明:dfn表示x点被DFS到的时间,low表示x和x所以子树结点最多只有指向x点的边,而没有指向x的祖先的边了。显然,遍历过的结点从x出发又最终回到x形成了一个环,即x点与它的子孙结点构成了强联通分量。

四、代码实现

根据定义,Tarjan算法按照以下步骤计算“追溯值”:

1.当节点x第一次被访问时,把x入栈,初始化 low[x]=dfn[x]。

2.扫描从x出发的每条边(x,y)。

  • 若y没被访问过,则说明(x,y)是树枝边,递归访问y,从y回溯之后,令 low[x]=min(low[x],low[y])。
  • 若y被访问过并且y在栈中,则令 low[xmin(low[x],dfn[y])。

3.从x回溯之前,判断是否有 low[x]=dfn[x]。若成立,则不断从栈中弹出节点,直至x出栈。

技术图片

(这个板子好奇怪,感觉会炸的亚子技术图片

缩点后重建图:上述代码中,ins[x]表示x这个点所属的SCC编号(我也不知道为什么要用这个变量名)。那么建图的话,只需要遍历原有的边,如果边所对应的两个节点所属的SCC编号不同,那么则添边,最后得到一个有向无环图。

五、例题

[HAOI2006]受欢迎的牛

题目链接

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果 A 喜欢 B,B 喜欢 C,那么 A 也喜欢 C。牛栏里共有 N 头奶牛,给定一些奶牛之间的爱慕关系,请你算出有多少头奶牛可以当明星。

首先考虑求出所有的极大强连通分量,显然一个分量内的牛是相互“受欢迎”的。考虑将每个分量看作一个点,以原图中端点属于不同分量的点为边组成新的图G,那么显然G中是没有环的(否则与极大的条件矛盾),这样图必定存在至少一个点出度为0,如果有多个点出度为0,那么答案显然为0(这些点之间显然无法直接或间接存在“受欢迎”关系)。如果只有一个点出度为0,那么只要满足所有点都能到达它即可,否则答案为0。如果上述条件均满足,那么答案就是该点对应的原分量中的点数。

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const int N=1e4+5,M=5e4+5;
 5 int n,m,cnt,num,dfn[N],low[N],ins[N],st[N],top,x,y,v[N],ans,k[N];
 6 int hd[N],to[M],nxt[M];
 7 void add(int x,int y){
 8     to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
 9 } 
10 void tarjan(int x){
11     dfn[x]=low[x]=++num,st[++top]=x;
12     for(int i=hd[x];i;i=nxt[i]){
13         int y=to[i];
14         if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
15         else if(!ins[y]) low[x]=min(low[x],dfn[y]);
16     }
17     if(low[x]==dfn[x]){
18         ins[x]=++cnt,++k[cnt];
19         while(st[top]!=x) ++k[cnt],ins[st[top]]=cnt,--top;
20         --top;
21     }
22 }
23 signed main(){
24     //freopen(".in","r",stdin);
25     //freopen(".out","w",stdout);
26     scanf("%lld%lld",&n,&m);
27     for(int i=1;i<=m;i++){
28         scanf("%lld%lld",&x,&y);
29         add(y,x);
30     }
31     cnt=0,x=0;
32     for(int i=1;i<=n;i++)
33         if(!dfn[i]) tarjan(i);
34     for(int i=1;i<=n;i++)
35         for(int j=hd[i];j;j=nxt[j])
36             if(ins[i]!=ins[to[j]]) v[ins[to[j]]]++;
37     for(int i=1;i<=cnt;i++)
38         if(!v[i]) ans=k[i],x++;
39     if(x==1) printf("%lld
",ans);
40     else puts("0");
41     return 0;
42 }

 

POJ1236 Network of Schools

题目链接

题目大意:给定一个有向图,N个点,求:

(1) 至少要选几个顶点, 才能做到从这些顶点出发, 可以到达全部顶点。
(2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部项点。

Tarjan算法求SCC,并缩点建图。

那么对于问题1,新的图中入度为0的点的点数即是答案。

对于问题2,答案为max (入度为0的点的点数,出度为0的点数),因为对于每个入度或出度为0的点,需要连一条边来解决,那么将出度为0的点连向入度为0的点是最优的。

此外,如果最后SCC只有1个,那么问题2的答案应该特判为0。

1 #include<bits/stdc++.h>
2 #define int long long
3 using namespace std;
4 signed main(){
5     //freopen(".in","r",stdin);
6     //freopen(".out","w",stdout);
7     puts("Dlstxdy,lkytsdy!");
8     return 0;
9 }

显然这个代码是过不了这道题的技术图片技术图片技术图片

 

(待更新,先占个坑技术图片欢迎纠错技术图片

以上是关于Tarjan算法 有向图SCC的主要内容,如果未能解决你的问题,请参考以下文章

有向图的强连通算法 -- tarjan算法

强连通分量的Tarjan算法

有向图强连通分量的Tarjan算法

转有向图强连通分量的Tarjan算法

BYV--有向图强连通分量的Tarjan算法

图之强连通强连通图强连通分量 Tarjan算法