这前几天看@liuyonglin和@steven两位巨佬在肝强连通分量,就也跟着潮流学了一下QWQ(多数都是两位大佬给讲的)
先介绍一下强连通分量:
定义:在一个有向图G(V,E)中,某子图G'(V',E')⊆G(V,E)满足:
- ∀a,b∈V'(a≠b)有a->b且b->a
∀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--;
}
}