图的连通性之强连通分量缩点建图
关 于 图 的 连 通 性
无向图:连通、连通图、连通分量、双连通分量
有向图:强连通、强连通图、强连通分量


Tarjan算法求强连通分量:
本质上就是dfs,就是在 dfs 的过程维护两个数组 dfn[] 、low[] ,
其中,dfn[ x ] 表示 dfs 到 x 点的最小的时间,即时间戳,可知在一个dfs树的子树中,dfn[x] 值越小,则其越浅。
low[ x ]表示在 dfs 树中,x 点及其后代指出去的边,能返回到的最浅的点的时间戳,即使 x 能够指向的最浅的节点

右边就是左边这棵树的搜索树。
5号点的 low 不能指向更浅的时间戳了,就指向自己,自己就是一个独立的强连通分量。
6返回到了最浅的时间戳5(4号点),它的 low 值就是5
3指向的最浅的dfs序就是1
那么,就会发现,当一个节点的 dfn[x] 和 low[x] 相等时,以它为根的一些子树就是强连通分量。

接下来理解一下这个栈是怎么运行的
一开始,先不断搜索,a、b、c、e分别入栈,这时还没开始回溯。
接下来,发现e 已经不能往下搜索了,就会判断这两个dfn[x] 和 low[x]是否相等,发现相等了,就把 e 节点弹出

接着,把 d 节点入栈,而 d 节点的下一条边指向一个更浅的节点 b ,其low[x]值 ( 以及 c 的low[x]值,因为它的儿子能指向更小的点 那么它也一定能指向更小的点 ) 就被更新为2

然后,d不能往下搜索了,又开始回溯,发现 d、c 的 dfn[x] 和 low[x] 不相等,知道 b 才相等,则在栈中以 b为根的子树是一个强连通分量,
就从栈中一直弹出弹到根 b 为止,也就得到了由 b、c、d组成的强连通分量。

接下来就回溯到了 a,a再指向 f ,就把f入栈,f 又指向 g ,再回溯更新low[x]的值,最后就又得到了强连通分量

这就是Tajan算法求强连通分量了。

得到强连通分量后,就可以缩点建图了。

即
会缩成

遍历就是 1 - 2 边的两个顶点的SCC编号一样,那就不加边。而 3 - 4 边的两个顶点的SCC编号不同,那么就属于两个强连通分量,就要加上这条边。
例题:POJ-1236
https://vjudge.net/problem/POJ-1236
题目大意
问,对于一个DAG(有向无环图):
1.至少要选几个点,才能从这些点出发到达所有点
2.至少加入几条边,就能从图中任何一个点出发到达所有点
分析
先求DAG的强连通分量数,再缩点,可以用tarjan算法来做。
第一个问题:不难想到答案就是缩点之后入度为0的点的个数
第二个问题:设缩点后入度为0的个数是n,出度为0的个数是m,至少添加边的条数就是max(n,m)
#include <cstdio> #include <cstring> #include <algorithm> #define maxn 101 using namespace std; int dfn[maxn],low[maxn],scc[maxn],stk[maxn],index=0,sccnum=0,top=0; struct node{ int to,next; }edge[maxn*maxn]; int head[maxn*maxn]; int cnt=1; inline void add(int u,int v){ edge[cnt].to = v; edge[cnt].next = head[u]; head[u] = cnt++; } inline void tarjan(int root){ //Tarjan算法求强连通分量缩点 if( dfn[root] ) return; dfn[root] = low[root] = ++index; stk[++top] = root; for(int i=head[root]; i; i=edge[i].next){ int v = edge[i].to; if( !dfn[v] ){ //如果v节点还没访问过 tarjan(v); low[root] = min(low[root],low[v]); //回溯回来更上儿子的进度(如果儿子能够 } //指向更浅的节点,那么它也一定行) else if( !scc[v] ){ //如果还在栈内 low[root] = min(low[root],dfn[v]); // printf("low[%d]=%d\n", root,low[root]); } } if(low[root]==dfn[root]){ //后代不能找到更浅的节点 sccnum++; for(;;){ int x = stk[top--]; scc[x] = sccnum; // printf("%d %d\n", x,sccnum); if( x == root ) break; } } } int ind[maxn],out[maxn]; inline void init(){ memset(head,-1,sizeof(head)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(scc,0,sizeof(scc)); top=0,index=0,sccnum=0,cnt=1; } int main(){ int n; init(); scanf("%d",&n); int u,v; for(u=1; u<=n; u++){ while(scanf("%d",&v) && v){ add(u,v); } } for(int i=1; i<=n; i++){ if(!dfn[i]){ tarjan(i); } } for(u=1; u<=n; u++){ for(int i=head[u]; i; i=edge[i].next){ v=edge[i].to; if(scc[u]!=scc[v]){ // printf("%d %d \n", scc[v],scc[u]); ind[ scc[v] ]++; out[ scc[u] ]++; } } } int ans1=0,ans2=0; for(int i=1; i<=sccnum; i++){ if(ind[i]==0) ans1++; if(out[i]==0) ans2++; } ans2 = max(ans1,ans2); if(sccnum==1) printf("1\n0\n"); else printf("%d\n%d\n", ans1,ans2); }

浙公网安备 33010602011771号