强连通分量-缩点-tarjan

这前几天看@liuyonglin和@steven两位巨佬在肝强连通分量,就也跟着潮流学了一下QWQ(多数都是两位大佬给讲的)

先介绍一下强连通分量:
定义:在一个有向图G(V,E)中,某子图G'(V',E')⊆G(V,E)满足:

  1. ∀a,b∈V'(a≠b)有a->b且b->a
  2. ∀G''(V'',E'')⊆G(V,E),且G''满足条件1,有V'⊄V''

众所周知,最短路算法在有负环的时候是不成立的,而强连通分量和环是等价的我们可以在判断出强连通分量后,把它缩成一个点,然后就可以跑最短路、拓扑排序等等算法了(撒花)

我们现在需要掌握两种算法:tarjan和kosaraju(后面一种机房网管讲过),这里主要讲一下tarjan

这是一个例子,tarjan有两个时间戳dfn、low,定义dfn(u)为节点u搜索的次序编号,low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号,由定义可知,当dfn[u]=low[u]时,以u为根的搜索子树上所有节点是一个强联通分量,这里用染色的方法来区分,过程中用zhan存储,low[u]在每次返回时更新,low[u]=min(low[u],low[v])(v是从哪里返回),若u还没有被染色,且v在zhan中,还需执行low[u]=min(low[u],dfn[v]),模拟一下这个过程:
从1开始,此时dfn[1]=low[1]=1,到3,此时dfn[3]=low[3]=2,再到5,此时dfn[5]=low[5]=3,到6,现在情况如图所示

到6的时候dfn[6]=low[6],所以以6为根的搜索子树为一个强连通分量,这里只需要退zhan退到6为止即可,发现只有一个6,颜色变成1(注意这里对于dfn等不等于low的判断是在枚举完所有边之后进行的)
退到5,发现dfn[5]=low[5],5为一个强联通分量,颜色变成2,退到3

从3到4,此时dfn[4]=low[4]=4,发现从4可以到1,1已经在zhan中,所以low[4]=min(low[4],low[1])=1

回到1,再从1到2,再从2到4,发现4在zhan中,所以low[2]=min(low[2],dfn[4])=dfn[4]=5,退回1,发现dfn[1]=low[1],退zhan到1,发现1,2,3,4为一个强连通分量,颜色为3

至此tarjan就完美结束了,判断出了强联通分量,并将其染色,剩下的重建边就很简单了(帮lyl补充一下最大半连通子图的知识)

上板子

int dfn[1005],low[1005],co[1005],num,st[1005],top,col;
struct edge{
	int l,r,v;
};
vector<edge> tmp[1005];
void tarjan(int u)
{
	dfn[u]=low[u]=++num;
	st[++top]=u;
	for(int i=0;i<tmp[u].size();i++)
	{
		int v=tmp[u][i].r;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!co[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])
	{
		co[u]=++col;
		while(st[top]!=u)
		{
			co[st[top]]=col;
			top--;
		}
		top--;
	}
}
posted @ 2019-07-12 14:16  kirito_raligun  阅读(210)  评论(0编辑  收藏  举报