求解强连通分量
目的
求解强连通分量,缩环,利用节点的sccno编号重新建图,达到题目的要求或者将原图转为DAG,进行图上DPor最短路算法
算法流程
变量声明:
dfn:该节点的dfs编号
low:该节点及其后代能够追溯到的最早的祖先的dfn编号
1.到达新节点,更新dfn,将low初始化为本身的dfn序号,并将其压入栈中
2.遍历u的子节点v,
如果dfn[v]=0,即v还没有被访问过,那么< u,v >是一条树边,v是u的后代,对v进行tarjian操作,根据low数组的定义,v能到的的low,u也能到达,所以用low[v]更新low[u]
如果dfn[v]!=0&&sccno[v]!=0,说明v是u的祖先,且v不属于之前的强连通分量,那么用dfn[v]更新low[u]
3.如果dfn[u]==low[u]说明u节点是一个强连通分量的第一个节点,那么将栈内元素弹出,直至弹出元素为u,目的是将不同的scc区分开来。
细节理解
dfn[v]!=0&&sccno[v]!=0,v号节点必须不属于其他scc的原因
原理不会,但是我有实例啊
如图,u是下面的节点,v是上面的被指向的节点。显然v在一个scc中,u自己是一个scc
此时,如果用dfn[v]更新low[u],那么low[u]!=dfn[u],在之后的出栈操作中,u就不会被当做一个单独的强连通,这与图示不符。
模板
void tarjian(int u){
dfn[u]=low[u]=++dfn_cnt;
s[++top]=u;
for(int i=fisrt[u];i!=-1;i=next[i]){
int v=e[i].t;
if(!dfn[v]){
tarjian(v);
low[u]=min(low[u],low[v]);
}
else if(!sccno[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
++scc_cnt;
for(;;){
int x=s[top];
top--;
sccno[x]=scc_cnt;
if(x==u) break;
}
}
return ;
}
粘一发black学长http://blog.csdn.net/loi_black的板子,black学长新开数组in_stack记录同一强连通分量,避免了对!sccno[v]的理解,感谢教练教我tarjian
void group(int x)
{
dfn[x]=low[x]=++tot; //累加遍历序号
stack[++snum]=x; //把这个点压入栈内
in_stack[x]=1; //表示这个元素在栈内存在
for(int i=first[x];i;i=next[i])
{
int u=hh[i].t;
if(!dfn[u]) //这个点没有搜过,所以这条边是树边
{
group(u);
low[x]=min(low[x],low[u]); //用low值更新low值
}
else if(in_stack[u]) //搜过且在栈内,他们属于一个强连通分量,这条边是一条非树边
low[x]=min(low[x],dfn[u]); //用dfn更新它的low值
}
if(dfn[x]==low[x]) //x是这个强连通分量中在dfs时最先搜到的点
{
cnt++;
while(true)
{
jlqlt[stack[snum]]=cnt; //栈内的元素都是在一个强连通分量里面
size[cnt]++; //这个是记录每个强连通分量的大小
in_stack[stack[snum]]=0; //弹栈
snum--;
if(stack[snum+1]==x) //一直到弹到属于这个强连通分量中的元素全被弹干净
break;
}
}
}