众所周知,tarjan算法的代码模板如下。
void tarjan(int u)
{
dfn[u]=low[u]=++timestap;
stk[++top] = u,in_stk[u] = true;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stk[v])
{
low[u]=min(low[u],dfn[v]);//!!!!!!!!!!!!!!!!!!!!!!!!!1
}
}
if(dfn[u]==low[u])
{
int y;
++scc_cnt;
do
{
y=stk[top--];
in_stk[y]=false;
id[y]=scc_cnt;
Size[scc_cnt] ++;
}while(y!=u);
}
}
但是我在读了算法竞赛进阶指南等其他书之后,发现当(x,y)边中指向的y在栈中时(也就是!!!处),
更新方式有low[u]=min(low[u],low[v]);和low[u]=min(low[u],dfn[v]);两种。
这两种方式是一样的吗?
回答:其实两种写法都对,只是理解不同。
注意到一件事情:我们并不是把low值编号直接当做了scc的属性编号。这也是保证两种做法都对的原因。
下面说下两种理解方式:
1、low[u]=min(low[u],dfn[v]):
引入记号:subtrree(x)为搜索树中以x为根的子树。
则low[x]定义为满足以下条件的节点的最小时间戳(dfn)
(1)该点在栈中
(2)存在一条从subtree(x)出发的的点,以该点为终点。
2、low[u]=min(low[u],low[v]):
则low[x]定义为满足以下条件的节点的最小时间戳(dfn)
(1)该点在栈中
(2)该点所能到达的最小的时间戳的
理解:注意到只有横插边或者后向边才可能构成环。对于后向边,此时其到达的节点y的low[y]=dfny。而对于横插边。若y已经能到达一个dfn比其小的点z,z若遍历完了所有子节点,则说明已经构成了强联通分量,不可能在栈中。因此,其必然能到达x的祖先节点,且此祖先节点恰好是与y这一枝的分叉点z。
总而言之 ,两种写法都对