引入
tarjan算法有两个最重要东西
dfn[x]表示x的dfs序
low[x]表示x以及x能连到的点中(包括间接连到的)最小的dfn
还有两个标记
一个表示是否访问过
一个为是否在当前的栈中
求解
low何时能更新呢
设当前点为v,子节点为u
1、u没有访问过
那么先递归进去然后low[v]=min(low[u])
2、u访问过但在当前的栈中
这说明u为v的祖先,那么只能low[v]=min(dfn[u])
另外
除了强联通分量以外,tarjan还可以求桥边,割点等
求割点:任意一个
low[u]>=dfn[v]
Code
这个代码是单向图中,所以判断简单一点
如果在双向图中,需要复杂一点的判断
void tarjan(int x)
{
low[x]=dfn[x]=++tot;p[++p[0]]=x;bz[x]=bz2[x]=1;
for(int i=last[x];i;i=next[i])
{
int y=to[i];
if(!bz[y])
{
tarjan(y);low[x]=min(low[x],low[y]);
}
else if(bz2[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x])
{
totot++;
for(;p[p[0]+1]!=x;p[0]--)
{
int k=p[p[0]];c[k]=totot;bz2[k]=0;
}
}
}
然而有些题目的图是一条链的,会爆栈
人工栈版Tarjan
为了方便(复制)起见,这里调用tarjan(x)和上面递归的调用tarjan(x),效果是完全一样的
c是染色数组
void tarjan1()
{
int x=zx[o];low[x]=dfn[x]=++tot;p[++p[0]]=x;bz[x]=bz2[x]=1;
for(int i=last[x];i;i=next[i])
{
if(!bz[to[i]])
{
zk[o]=1;zi[o]=i;zx[++o]=to[i];zk[o]=0;return;
}
else if(bz2[to[i]]) low[x]=min(low[x],dfn[to[i]]);
}
if(low[x]==dfn[x])
{
totot++;
for(;p[p[0]+1]!=x;p[0]--) c[p[p[0]]]=totot,bz2[p[p[0]]]=0;
}
o--;
}
void tarjan2()
{
int x=zx[o];low[x]=min(low[x],low[to[zi[o]]]);
for(int i=next[zi[o]];i;i=next[i])
{
if(!bz[to[i]])
{
zk[o]=1;zi[o]=i;zx[++o]=to[i];zk[o]=0;return;
}
else if(bz2[to[i]]) low[x]=min(low[x],dfn[to[i]]);
}
if(low[x]==dfn[x])
{
totot++;
for(;p[p[0]+1]!=x;p[0]--) c[p[p[0]]]=totot,bz2[p[p[0]]]=0;
}
o--;
}
void tarjan(int x)
{
zx[++o]=x;zk[o]=0;zi[o]=0;
while(o>0){if(zk[o]==0) tarjan1(); else tarjan2();}
}
例题
【NOIP2016提高A组8.12】通讯
【NOIP2016提高A组8.11】种树
【NOIP2016提高A组模拟9.9】爬山
【NOIP2017提高A组冲刺11.5】轰炸