返回顶部

连通性问题——Tarjan

参考自:OI Wik

Tarjan 算法求强连通分量

在 Tarjan 算法中为每个结点x维护了以下几个变量:

  1. dfn[x]:深度优先搜索遍历时结点x被搜索的次序,搜索到一个节点即入栈,做入栈标记。
  2. low[x]:在x的子树中能够回溯到的最早的已经在栈中的结点。设以x为根的子树为son[x]。low定义为以下结点的dfn的最小值:son[x]中的结点;从son[x]通过一条不在搜索树上的边能到达的结点。

一个结点的子树内结点的 dfn 都大于该结点的 dfn。

从根开始的一条路径上的 dfn 严格递增,low 严格非降。

按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索,维护每个结点的 dfn 与 low 变量,且让搜索到的结点入栈。每当找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。在搜索过程中,对于结点x和与其相邻的结点v(v不是x的父节点)考虑 3 种情况:

  1. v未被访问:继续对v进行深度搜索。在回溯过程中,用low[v]更新low[x]。因为存在从x到v的直接路径,所以v能够回溯到的已经在栈中的结点,x也一定能够回溯到。
  2. v被访问过,已经在栈中:根据 low 值的定义,用dfn[v]更新low[x]。
  3. v被访问过,已不在栈中:说明v已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个x使得dfn[x]==low[x]。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 dfn 和 low 值最小,不会被该连通分量中的其他结点所影响。

因此,在回溯的过程中,判定dfn[x]==low[x]是否成立,如果成立,则栈中x及其上方的结点构成一个 强连通分量,则不停出栈,做出栈标记,直到x出栈。

int dfn[N],low[N],z[N],bh[N],nu[N];
//z[i]:栈;bh[i]:i所属强连通分量的编号;nu[i]:强连通分量i内的节点数; 
void dfs(int x)
{
    dfn[x]=low[x]=++num;
    z[++top]=x,pd[x]=1;
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            dfs(v);
            low[x]=min(low[x],low[v]);
        }
        else if(pd[v])
            low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x])
    {
        int vv=z[top--];
        bh[vv]=++cnt;
        nu[cnt]++; 
        pd[vv]=0;
        while(vv!=x)
        {
            vv=z[top--];
            bh[vv]=cnt;
            nu[cnt]++;
            pd[vv]=0;
        }
    }
}

例题:

P2863 [USACO06JAN]The Cow Prom S

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=5e4+5;
int n,m,tot,num,top,ans;
int dfn[N],low[N],z[N];
int fi[N],ne[M],to[M];
bool pd[N];

void add(int x,int y)
{
    ne[++tot]=fi[x];
    fi[x]=tot;
    to[tot]=y;
}

void dfs(int x)
{
    low[x]=dfn[x]=++num;
    z[++top]=x,pd[x]=1;
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            dfs(v);
            low[x]=min(low[x],low[v]);
        }
        else if(pd[v])
            low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x])
    {
        int vv=z[top--];
        pd[vv]=0;
        int s=1;
        while(vv!=x)
        {
            vv=z[top--];
            s++;
            pd[vv]=0;
        }
        if(s>1) ans++;
    }
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            dfs(i);
    cout<<ans<<'\n';
}

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=5e4+5;
int n,m,tot,top,num,cnt,ans;
int dfn[N],low[N],z[N],bh[N],nu[N];
int fi[N],ne[M],to[M];
bool pd[N],d[N];

void add(int x,int y)
{
    ne[++tot]=fi[x];
    fi[x]=tot;
    to[tot]=y;
}

void dfs(int x)
{
    dfn[x]=low[x]=++num;
    z[++top]=x,pd[x]=1;
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            dfs(v);
            low[x]=min(low[x],low[v]);
        }
        else if(pd[v])
            low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x])
    {
        int vv=z[top--];
        bh[vv]=++cnt;
        nu[cnt]++; 
        pd[vv]=0;
        while(vv!=x)
        {
            vv=z[top--];
            bh[vv]=cnt;
            nu[cnt]++;
            pd[vv]=0;
        }
    }
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            dfs(i);
    for(int i=1;i<=n;i++)
        for(int j=fi[i];j;j=ne[j])
            if(bh[i]!=bh[to[j]])
                d[bh[i]]=1;
    int s=0;
    for(int i=1;i<=cnt;i++)
        if(!d[i])
            s++,ans=i;
    if(s==1) cout<<nu[ans]<<'\n';
    else cout<<'0'<<'\n';
}
posted @ 2022-10-09 20:16  光暗之影x  阅读(19)  评论(0)    收藏  举报