1. 程式人生 > >有向圖的強連通分量(tarjan演算法)

有向圖的強連通分量(tarjan演算法)

強連通分量

有向圖強連通分量:在有向圖G中,如果兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。

考慮強連通分量C,設其中第一個被發現的點為x,則,C中其他的點都是x的後代。我們希望在x訪問完成時立即輸出C(可以同時記錄C,輸出代表當前在當前的遍歷序列中剔除),這樣就可以在同一顆DFS樹種區分開所有SCC了,因此問題的關鍵是判斷一個點是否為一個SCC中最先被發現的點。

SDUT3262

利用targan演算法求出圖中所有的強連通分量,將相同連通分量的點縮成一個點,然後重新構圖,BFS求最短路即可。

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define INF 0x3f3f3f3f
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int N = 100010;
int low[N];//當前能回溯到的棧中最小的次序號
int pre[N]; // 記錄當前節點的次序號。(時間戳)
int scc[N];//記錄節點所屬強連通分量
stack<int>st;//棧中儲存當前未處理的節點(訪問了,但並沒有劃分為連通分量)
int dfs_num;//次序號
int cnt;//強連通分量的編號 1 ~ maxNumberOfSCC
int n,m;
int dis[N];
vector<int>V[N],G[N];
void init(){
    mem(low,0);mem(pre,0);mem(scc,0);
    dfs_num = 0; cnt = 0;
    while(!st.empty()) st.pop();
    for(int i=0;i<=n;i++){
        V[i].clear();G[i].clear();
    }
}
void tarjan(int x){
    pre[x] = low[x] = ++dfs_num;
    st.push(x);
    for(int i=0;i<V[x].size();i++){
        int v = V[x][i];
        if(!pre[v]){//沒有訪問過
            tarjan(v);
            low[x] = min(low[x],low[v]);
        }
        else if(!scc[v]){// 訪問過了,但是沒有劃分聯通分量,
                //也就是在棧中
            low[x] = min(low[x],pre[v]); // pre[v],v節點的時間戳
        }
    }
    if(low[x] == pre[x] ){//當前的次序號等於 能回溯到的最小次序號
                     //說明找到了”根“節點
        cnt++;
        while(1){
             int tmp = st.top(); st.pop();
             scc[tmp] = cnt;
             if(tmp == x) break;
        }
    }
}
int bfs(int x){
    queue<int>Q;
    Q.push(x);
    mem(dis,-1);
    dis[x] = 0;
    while(!Q.empty()){
        int u = Q.front();Q.pop();
        for(int i=0;i<G[u].size();i++){
            int v = G[u][i];
            if(dis[v] == -1){
                dis[v] = dis[u] + 1;
               // cout<<dis[v]<<" sdsd"<<endl;
                if(v == scc[n-1]) return dis[v];
                Q.push(v);
            }
        }
    }
    return dis[scc[n-1]];
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        init();
        int a,b;
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            V[a].push_back(b);
        }
        for(int i=0;i<n;i++){
            if(!pre[i])
                tarjan(i);//縮點
        }
       // for(int i=0;i<n;i++)
         //   cout<<scc[i]<<endl;
        for(int i=0;i<n;i++){ //重新構圖
            for(int j=0;j<V[i].size();j++){
                int v = V[i][j];
                if(scc[i] != scc[v])
                    G[scc[i]].push_back(scc[v]);
            }
        }
        int ans = bfs(scc[0]);
        printf("%d\n",ans);
    }
}

HDU1269

Tarjan 裸題

直接求強連通分量,連通分量數為1即輸出Yes(強連通圖)。

POJ1236

英語太爛,題目沒讀太懂。。

建圖,求強連通縮點,出來新圖DAG,然後找所有入度為0的頂點,即為問題一的答案。(顯然)

有這麼一個定理,對於一個DAG(Directed Acyclic Graph),設所有入度為0的頂點數為n,所有出度為0的頂點數為m,則至少加 max(n,m)條邊可形成一個強連通分量。

坑點,當給定的圖是一個強連通圖,即只有一個強連通分量時,不能用求答案2的方法去求 加多少條邊,因為顯然不用加邊了。

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define INF 0x3f3f3f3f
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn = 100 + 10;
vector<int>G[110];
vector<int>V[110];
stack<int>st;
int pre[maxn];
int low[maxn];
int scc[maxn];
int dfs_num;int cnt;
void init(){
    mem(pre,0);
    mem(low,0);
    mem(scc,0);
    dfs_num = 0,cnt = 0;
    while(!st.empty()) st.pop();
    for(int i=1;i<=100;i++){
        V[i].clear();G[i].clear();
    }
}
void tarjan(int x){
    pre[x] = low[x] = ++dfs_num;
    st.push(x);
    for(int i=0;i<V[x].size();i++){
        int v = V[x][i];
        if(!pre[v]){
            tarjan(v);
            low[x] = min(low[x],low[v]);
        }
        else if(!scc[v]){
            low[x] = min(low[x],pre[v]);
        }
    }
    if(low[x] == pre[x]){
        cnt++;
        while(1){
            int tmp = st.top();st.pop();
            scc[tmp] = cnt;
            if(tmp == x) break;
        }
    }
}
int main(){
    int n;
    scanf("%d",&n);
    init(); int tmp;
    for(int i=1;i<=n;i++){
        while(scanf("%d",&tmp)&& tmp){
            V[i].push_back(tmp);
        }
    }
    for(int i=1;i<=n;i++){
        if(!pre[i])
            tarjan(i);
    }
    //for(int i=1;i<=n;i++)
      //  printf("%d\n",scc[i]);
    if(cnt == 1){
        printf("1\n0\n"); return 0;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<V[i].size();j++){
            int v = V[i][j];
            if(scc[i] != scc[v]){
                G[scc[i]].push_back(scc[v]);
            }
        }
    }
    int zeroOutDegree  = 0;
    int zeroInDegree  = 0;
    int indegree[110]; mem(indegree,0);
    for(int i=1;i<=cnt;i++){
        if(G[i].size() == 0)
            zeroOutDegree++;
        for(int j=0;j<G[i].size();j++){
            int v = G[i][j];
            indegree[v]++;
        }
    }
    for(int i=1;i<=cnt;i++){
        if(indegree[i] == 0)
            zeroInDegree++;
    }
    printf("%d\n",zeroInDegree);
    printf("%d\n",max(zeroInDegree,zeroOutDegree));
    return 0;
}

POJ2186

題目大意,有很多牛和牛的關係(A,B)代表A認為B受歡迎,(B,C)代表B認為C受歡迎,這種關係可以傳遞,所以A也認為C受歡迎。

給出這些關係,問一共有多少牛受所有牛的歡迎。

1.求出所有的強連通分量,用tarjan演算法
2.每個強連通分量縮成一點,則形成一個有向無環圖DAG。
3.DAG上面如果有唯一的出度為0的點,則改點能被所有的點可達。
  那麼該點所代表的連通分量上的所有的原圖中的點,都能被原圖中
  的所有點可達 ,則該連通分量的點數就是答案。
4.DAG上面如果有不止一個出度為0的點,則這些點互相不可達,原問題
  無解,答案為0;               by kuangbin

想出DAG中存在唯一出度為0的點可以被所有點可達就OK了

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define INF 0x3f3f3f3f
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int N = 10010;
int low[N];//當前能回溯到的棧中最小的次序號
int pre[N]; // 記錄當前節點的次序號。(時間戳)
int scc[N];//記錄節點所屬強連通分量
stack<int>st;//棧中儲存當前未處理的節點(訪問了,但並沒有劃分為連通分量)
int dfs_num;//次序號
int cnt;//強連通分量的編號 1 ~ maxNumberOfSCC
int n,m;
int dis[N];
int numOfscc[N];
vector<int>V[N],G[N];
void init(){
    mem(low,0);mem(pre,0);mem(scc,0);mem(numOfscc,0);
    dfs_num = 0; cnt = 0;
    while(!st.empty()) st.pop();
    for(int i=0;i<=n;i++){
        V[i].clear();G[i].clear();
    }
}
void tarjan(int x){
    pre[x] = low[x] = ++dfs_num;
    st.push(x);
    for(int i=0;i<V[x].size();i++){
        int v = V[x][i];
        if(!pre[v]){//沒有訪問過
            tarjan(v);
            low[x] = min(low[x],low[v]);
        }
        else if(!scc[v]){// 訪問過了,但是沒有劃分聯通分量,
                //也就是在棧中
            low[x] = min(low[x],pre[v]); // pre[v],v節點的時間戳
        }
    }
    if(low[x] == pre[x] ){//當前的次序號等於 能回溯到的最小次序號
                     //說明找到了”根“節點
        cnt++;
        int l = 0;
        while(1){
             int tmp = st.top(); st.pop();
             scc[tmp] = cnt; l++;
             if(tmp == x) break;
        }
        numOfscc[cnt] = l;
    }
}
int bfs(int x){
    queue<int>Q;
    Q.push(x);
    mem(dis,-1);
    dis[x] = 0;
    while(!Q.empty()){
        int u = Q.front();Q.pop();
        for(int i=0;i<G[u].size();i++){
            int v = G[u][i];
            if(dis[v] == -1){
                dis[v] = dis[u] + 1;
                if(v == scc[n-1]) return dis[v];
                Q.push(v);
            }
        }
    }
    return dis[scc[n-1]];
}
int main(){
    int t;
    while(scanf("%d%d",&n,&m)!=EOF){
        init();
        int a,b;
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            V[a].push_back(b);
        }
        for(int i=1;i<=n;i++){
            if(!pre[i])
                tarjan(i);//縮點
        }
        int ans = 0;
        int indegree[N];mem(indegree,0);
        //cout<<cnt<<endl;
        for(int i=1;i<=n;i++){ //重新構圖
            for(int j=0;j<V[i].size();j++){
                int v = V[i][j];
                if(scc[i] != scc[v]){
                    G[scc[i]].push_back(scc[v]);
                }
            }
        }
        int l = 0;
        for(int i=1;i<=cnt;i++){
            if(G[i].size() == 0){
                l++; ans = numOfscc[i];
            }
        }
        if(l == 1)
        printf("%d\n",ans);
        else
            printf("0\n");
    }
}