前言 :
很多次碰到tarjan, 然而一直没学会qwq,
今天终于看懂了, 写篇总结以后复习 。
先放一篇很好的博客:mengxiang000
思想:
首先,tarjan是一种求强联通分量的算法,基于dfs序。
主要是两个重要数组:
1.dfn【i】--i点dfs到的顺序。
2.low【i】--能到达i点的最早的点
(最早指最早遍历到,也就是dfs序最小)
low数组初始化:这个点的dfn值
那么第一个数组我们明白,第二个数组又有何用呢?慢慢来看下文证明
一:
强联通分量一定有环(如A->C->B),从另一个分量里冲进此强联通分量,立足的第一个点设为
环头(如虚点->A)。
二:
从环头(A)一直搜到环尾(B)后,环头已经搜过,所以开始回溯。
回溯过程包括 low【B】= min(low【B】,low【A】);(从 A 回溯到 B)
(理解:如果有一条边从u指向v,那么u的low值会与v的low值比较)
由于环头一定比环尾先搜到,1<3,所以环尾的low数组会被更新,
继续回溯,所有的环上的节点都会被更新。
三:
一直回溯(传递)到环头,环头A不会被更新(因为那就是他自己的dfn值啊qaq)
四:
此时遥望全环,难道还有谁没有臣服在环头的dfn值下?没错,环上所有点的low数组都存的是环头的dfs序,环头也是。
也就是说:low【环头】== dfn【环头】
备注(五):上句也可说明环头没被任何人更新过,也就是没有任何此联通量以外的点指向他(否则一定会被更新)
因此,我们只要判断谁的low值和dfn值相等,就能找到所有强联通分量里的大腿!
实现:
计数code:
void tarjan(int x){
vis[x] = 1;
dfn[x] = low[x] = ++ cnt; // 初始值
for(int i=fst[x]; i; i=nxt[i]) // 链式前向星
{
int v = to[i];
if(!dfn[v]) tarjan(v); //递归
if(vis[v]) low[x] = min(low[x], low[v]); //更新low值
}
if(low[x] == dfn[x])
ans ++; //计数
}
缩点code(别人的 :)):
void tarjan(int x)
{
low[x]=dfn[x]=++tim;
stac[++top]=x;vis[x]=1;
for (int i=head[x];i;i=edge[i].next)
{
int v=edge[i].to;
if (!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if (vis[v])
{
low[x]=min(low[x],low[v]);
}
}
if (dfn[x]==low[x])
{
int y;
while (y=stac[top--])
{
sd[y]=x;
vis[y]=0;
if (x==y) break;
p[x]+=p[y];
}
}
}
RP ++!