圖論-有向圖縮點

wust_cyl發表於2018-04-22

強連通(strongly connected): 在一個有向圖G裡,設兩個點 a b 發現,由a有一條路可以走到b,由b又有一條路可以走到a,我們就叫這兩個頂點(a,b)強連通。

強連通圖: 如果 在一個有向圖G中,每兩個點都強連通,我們就叫這個圖,強連通圖。

強連通分量strongly connected components):在一個有向圖G中,有一個子圖,這個子圖每2個點都滿足強連通,我們就叫這個子圖叫做 強連通分量 。

有向圖的縮點就是把有向圖中強連通分量縮成一個點(道理很簡單,我到了這個強連通分量的任何一點,那麼這個強連通分量就能被我訪問了),在處理有向圖的連通性問題時有很多作用。

比如如下問題:

給出一個 0 ≤ N ≤ 105 點數、0 ≤ M ≤ 105 邊數的有向圖,

輸出一個儘可能小的點集,使得從這些點出發能夠到達任意一點,如果有多個這樣的集合,輸出這些集合升序排序後字典序最小的。

第一行為兩個整數 1 ≤ n, m ≤ 105
接下來 M 行,每行兩個整數 1 ≤ u, v ≤ 105 表示從點 u 至點 v 有一條有向邊。

資料保證沒有重邊、自環。

第一行輸出一個整數 z,表示作為答案的點集的大小;

第二行輸出 z 個整數,升序排序,表示作為答案的點集。

7 10
4 5
5 1
2 5
6 5
7 2
4 2
1 2
5 3
3 5
3 6
2
4 7

不明白縮點的話,是很難做出來的,也有其他方法,這裡這是舉個例子來解決問題。

縮點其實難在求有向圖的強連通分量上面,這個得先去了解一下點選開啟連結

我們把輸出的程式碼改一下,記錄到一個陣列裡面並且同時給每一個點打上標記,即這些點都是一個強連通分量裡面的

num記錄的是強連通分量數量,p是記錄第幾個強連通分量裡面的點,unicom表示每一個點屬於第幾個強連通分量。

演算法複雜度O(N+E)

這樣我們的預備工作就做好了。把N個點劃分成了num個強連通分量塊了。

問了驗證有向圖的聯通性,我們還需要利用邊的資訊。

假如存a->b的邊而且unicom[a]!=unicom[b]即它們倆個點不屬於同一強連通分量。那麼也就是說我們到達了點a,那麼包括點b的強連通分量就全部可以到達了。

我們給強連通分量unicom[b]打上標記,這塊不用考慮了,考慮a就行了。

S陣列儲存起點 E陣列儲存終點  e表示邊的條數。

演算法複雜度O(E)

總時間複雜度O(E+N)

基本上這個連通性的問題就解決了,對於那些沒有打上標記的強連通分量就需要選取到達了。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+100;
vector<int> G[maxn],p[maxn],ans;
int low[maxn],dfn[maxn],Stack[maxn],S[maxn],E[maxn],n,e,cnt,Index,num;
int link[maxn],unicom[maxn];
bool vis[maxn];

void tarjan(int u)
{
    low[u]=dfn[u]=++cnt;
    vis[u]=true;
    Stack[++Index]=u;
    for (int i=0;i<G[u].size();i++) {
        int v=G[u][i];
        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]) {
       ++num;
       do {
            p[num].push_back(Stack[Index]);
            unicom[Stack[Index]]=num;
            vis[Stack[Index]]=false;
            Index--;
       }while (u!=Stack[Index+1]);
    }
    return ;
}
int main()
{
    scanf("%d%d",&n,&e);
    for (int i=1;i<=e;i++) {
        scanf("%d%d",&S[i],&E[i]);
        G[S[i]].push_back(E[i]);
    }
    memset (vis,false,sizeof(vis));
    for (int i=1;i<=n;i++) {
        if (!dfn[i]) tarjan(i);
    }
    for (int i=1;i<=e;i++) {
        if (unicom[S[i]]!=unicom[E[i]])
            link[unicom[E[i]]]=1;
    }
    for (int i=1;i<=num;i++) sort(p[i].begin(),p[i].end());
    for (int i=1;i<=num;i++) {
        if (!link[i]) ans.push_back(p[i][0]);//選取第一個點就好了
    }
    sort(ans.begin(),ans.end());
    printf("%d\n",ans.size());
    for (int i=0;i<ans.size();i++) {
        printf("%d%c",ans[i],(i==ans.size()-1)?'\n':' ');
    }
    return 0;
}










相關文章