很久之前就想学 T a r j a n Tarjan Tarjan了,然而由于各种奇怪的原因直到上过课才学会。
于是来写一篇学习笔记 & \& &题解。。。
T a r j a n Tarjan Tarjan是 R o b e r t    T a r j a n Robert\;Tarjan RobertTarjan发明的一种基于 D F S DFS DFS的求解强连通分量及割点等图论问题的算法,复杂度 O ( n + m ) O(n+m) O(n+m),十分高效。
T a r j a n Tarjan Tarjan使用一个栈来维护当前搜过的路径信息,并使用 D F S DFS DFS序时间戳来判断是否找到了环。
用 d f n i dfn_i dfni表示点 i i i的 D F S DFS DFS序, l o w i low_i lowi表示从点 i i i出发能到达的点中最小的 D F S DFS DFS序,每次遇到已入栈的点就不断退栈染色即可。。。
代码实现不算麻烦:
int cnt;
int col;
void tarjan(int u)
{
static stack<int> st;
low[u]=dfn[u]=++cnt;
vis[u]=1;
st.push(u);
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(vis[v])
{
low[u]=min(low[u],dfn[v]);
}
}
}
if(dfn[u]==low[u])
{
int t=-1;
++col;
while(t!=u)
{
t=st.top();
st.pop();
color[t]=col;
vis[t]=0;
}
}
}
如果要把求出的强联通分量缩点:
void shrink_point()
{
for(int i=1;i<=n;++i)
{
if(!dfn[i])tarjan(i);
}
for(int u=1;u<=n;++u)
{
for(int i=head[u];i;i=E[i].next)
{
int v=E[i].to;
if(color[u]!=color[v])
{
_add(color[u],color[v]);
}
}
}
}
求解 2 _ S A T 2\_SAT 2_SAT问题:
bool two_SAT()
{
for(int i=1;i<=n<<1;++i)
{
if(!dfn[i])tarjan(i);
}
for(int i=1;i<=n;++i)
{
if(color[i]==color[i+n])return 0;
}
return 1;
}
然后变量 a i a_i ai取 t r u e true true对应 [ 1 , n ] [1,n] [1,n]的点编号,取 f a l s e false false对应 [ n + 1 , 2 n ] [n+1,2n] [n+1,2n]的点编号。
然后 a i a_i ai的取值就是 c o l o r [ i ] > c o l o r [ i + n ] color[i]>color[i+n] color[i]>color[i+n]。
求解割点:
void tarjan(int u,int rt)
{
int rec=0;
static int cnt=0;
static stack<int> st;
low[u]=dfn[u]=++cnt;
vis[u]=1;
st.push(u);
for(int i=head[u];i;i=E[i].next)
{
int v=E[i].to;
if(!dfn[v])
{
tarjan(v,rt);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=rt)
{
cut[u]=1;
}
if(u==rt)
{
++rec;
}
}
low[u]=min(low[u],dfn[v]);
}
if(u==rt&&rec>1)cut[rt]=1;
}