1. 程式人生 > >有向圖的強連通分量之Tarjan演算法

有向圖的強連通分量之Tarjan演算法

描述:

To prove two sets A and B are equivalent, we can first prove A is a subset of B, and then prove B is a subset of A, so finally we got that these two sets are equivalent.
You are to prove N sets are equivalent, using the method above: in each step you can prove a set X is a subset of another set Y, and there are also some sets that are already proven to be subsets of some other sets.


Now you want to know the minimum steps needed to get the problem proved.

題意:給出n個命題,其中m條以推匯出,讓你求最少還需要推到多少個讓所有命題等價。

分析:我們把每個命題看做是節點,推到看作是有向邊,那麼題目可以轉化為n個頂點m條邊的有向圖,要求新增最少的邊,使得到的圖強連通。

定義

連通分量:相互可達的節點稱為連通分量(connecteed component)。

性質:在無向圖中,如果從節點 u 可到節點 v ,那麼從節點v 必然可到節點 u ,如果節點 u 可到節點 v ,而節點 v 又可達節點 w ,則必然節點 u 可達節點 w ,在加上每個節點

都可達自身,則發現無向圖的中存在關係滿足 自反性,對稱性,傳遞性。

強連通分量:“相互可達”為有向圖的強連通分量(Strongly Connected Componenet,SCC),可看做事一個集合。

如果把一個集合看成點,那麼所有的SCC構成一個SCC圖,這個SCC圖不會存在有向環,因此是一個有向無環圖 (DAG)。

求解:如何求解一個有向圖的強連通分量呢?Tarjan演算法就是很好的解決這個問題。

首先找題目中的強連通分量,把每一個強連通分量縮成一個點,得到一個DAG,那麼得到圖中所有點入度和出度的最大值就是答案。

首先就是通過一個DFS求出圖中的SCC數目,用棧s儲存當前scc中的節點,scc_cnt是scc計數器,而sccno [ i ] 為 i 所在的scc編號。

vector<int> G[N];
int pre[N],lowlink[N],sccno[N],dfs_clock,scc_cnt;
stack<int> s;

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    s.push(u);
    for(int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u]=min(lowlink[u],lowlink[v]);
        }
        else if(!sccno[v])
        {
            lowlink[u]=min(lowlink[u],pre[v]);
        }
    }
    if(lowlink[u]==pre[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x=s.top();
            s.pop();
            sccno[x]=scc_cnt;
            if(x==u)
                break;
        }
    }
}
void find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0; i<n; i++)
        if(!pre[i]) dfs(i);
}

程式碼:
#include <cstdio>
#include <vector>
#include <iostream>
#include <stack>
#include <cstring>
using namespace std;
const int N = 25000;
vector<int> G[N];
int pre[N],lowlink[N],sccno[N],dfs_clock,scc_cnt;
stack<int> s;

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    s.push(u);
    for(int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u]=min(lowlink[u],lowlink[v]);
        }
        else if(!sccno[v])
        {
            lowlink[u]=min(lowlink[u],pre[v]);
        }
    }
    if(lowlink[u]==pre[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x=s.top();
            s.pop();
            sccno[x]=scc_cnt;
            if(x==u)
                break;
        }
    }
}
void find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0; i<n; i++)
        if(!pre[i]) dfs(i);
}
int in[N],out[N];

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; i++)
            G[i].clear();
        for(int i=0; i<m; i++)  //存圖
        {
            int u,v;
            scanf("%d%d",&u,&v);
            u--;
            v--;
            G[u].push_back(v);
        }
        find_scc(n);
        for(int i=1; i<=scc_cnt; i++)  
            in[i]=out[i]=1;
        for(int u=0; u<n; u++)
        {
            for(int i=0; i<G[u].size(); i++)
            {
                int v=G[u][i];
                if(sccno[u] != sccno[v])
                    in[sccno[v]]=out[sccno[u]]=0;
            }
        }
        int a=0,b=0;
        for(int i=1; i<=scc_cnt; i++)
        {
            if(in[i])
                a++;
            if(out[i])
                b++;
        }
        int ans=max(a,b);
        if(scc_cnt == 1)
            ans=0;
        if(m==0&&n!=1)
            ans=n;
        printf("%d\n",ans);
    }
    return 0;
}