强连通分量仍然是信息学中的基础内容,在图论中和连通分量一样都是相当重要的东西。而强连通分量类型的题不知为什么出现频率很高,比如缩环什么的,考了好多次……
先定义强连通分量。同样设有一个有向图
G={V,E}
,同时其中有一个子图
G′={V′,E′}
,使得任意两点
i,j∈V′
都能互相到达。这叫做连通分量。而强连通分量,就是
G′
的最大化(当然,图中有可能有多个孤立的强连通分量)。强连通分量,在某种意义上来说,就是一个大环。
那么怎么求呢?
我们考虑从某一点开始DFS,如果某个子图
G′={V′,E′}
是强连通分量,那么这个强连通分量内部必定有至少一条边从访问较后的点连到访问较前的点。换个说法,假设我们现在有一棵DFS树,其中某棵子树(这里的“子树”不是通常的子树,这里的“子树”允许不完全包含通常子树的某些节点)所对应的点集构成了强连通分量,那么必然每个叶子节点都直接或间接地能到达这棵子树的根节点,再换句话说,就是至少有一个叶子节点向根节点直接连边。
另外证明一样东西:一个强连通分量不可能不只是一棵子树。用反证法,设有一个强连通分量在DFS树上占两棵子树,然后容易看出,其中先访问的那棵子树的任意一个节点都可以到达后访问的那棵子树的所有节点,于是,由DFS的算法可知,DFS算法如果要访问那棵先访问的子树的对应子图,那么必然也会在这个访问过程中访问到另一棵子树所对应的子图(因为是DFS,“一条路走到黑”),再换句话说,就是后访问的子树必然要包含在先访问的子树里,于是我们发现,它们其实是同一棵子树,两个的就证明完毕了,再用数学归纳法归纳一下,就都证出来啦。
好吧,说了这么一大段,怎么做呢?很简单。这里介绍Tarjan算法。Tarjan算法运用了时间戳的思想,若设
pre(i)
为点
i
第一次被访问到的时间,而
int nowt = 0;
int pre[MAXN];
int low[MAXN];
int dfs(int x){
//....
pre[x] = nowt++;
low[x] = pre[x];
//....
int nowlow = low[x];
//....
nowlow = min(nowlow,dfs(next));
//....
//(*)
return low[x] = nowlow;
}
于是,就像上面的那段代码一样,我们在DFS回来的时候再更新一遍
low(i)
,然后就做完了。
如果我们继续用一个栈来维护,比如说每访问到一个点就
push()
进去,然后当DFS回来(即大概是代码中(*)处)时遇到
low(i)=pre(x)
的点时,就意味着这个点是强连通分量中被首个访问到的点,于是就一直将栈中元素弹出直到弹出了这一个点为止,于是我们便已经弹出了一个强连通分量了。
如果要缩环之类的,倒也不难,重建一个新图就好了。
其实强连通分量本也不难,难的是思路,求强连通分量的过程用到了时间戳,所以还是得好好掌握啊。