強連通分量和二部圖

對於無向圖G: 如果兩個頂點之間有一條路徑連着, 我們就說這兩個頂點是連通的(無向圖的邊無方向性, 只要有邊連着就是連通的)。 如果滿足圖中的任意兩個頂點都是連通的, 我們就說圖是連通圖。 所謂的連通分量, 就是無向圖中的極大連通子圖。  對於連通圖, 只有一個連通分量, 就是它本身。 非聯通的無向圖有多個連通分量。 

 如下圖, 該無向圖的連通分量分別是: (A L M J B F C), (D E), (G, K, H, I)



當對無向圖進行遍歷的時候, 如果該無向圖是連通圖, 只需從圖的任一個頂點出發, 進行DFS(或者BFS), 便可以遍歷圖中所有的頂點。 對於非連通的無向圖, 則需要從多個頂點出發進行搜索。 而每一次從一個新的起始點出發進行搜索得到的頂點訪問序列恰爲各個連通分量的頂點集合。 不難看出,對無向圖進行的DFS調用的次數(指的是選擇新的起始點進行DFS)就是給定無向圖的連通分量的個數

對於有向圖, 對應的概念強連通分量。  

若對於V中任意兩個不同的頂點u和v,都存在從u到v以及從v到u的路徑(注意有向圖的邊是有方向的),則稱G強連通圖(Strongly Connected Graph)。相應地有強連通分量(Strongly Connected Component)的概念。強連通圖只有一個強連通分量,即是其自身;非強連通的有向圖有多個強連通分量。

尋找有向圖的強連通分量的算法用到了圖G = (V, E)的轉置。  所謂的轉置就是把有向圖的邊進行均做反向處理

假如圖G使用的是鄰接鏈表表示的, 那麼求取G的轉置圖像transpose(G)所需的時間就是線性時間O(V + E)。 

下面僞代碼, 通過使用兩次深度優先搜索計算有向圖G的強連通分量(一次對原始圖進行DFS, 記錄下結束訪問的時間。 最後再對 原始圖的轉置 按照結束訪問節點的時間的遞減順序進行一次DFS)

  

即包含如下三步:

(1)調用DFS(G)計算每一個節點的finishing time。 

(2)計算圖G的轉置圖

(3)按照所有節點的結束時間遞減的順序訪問節點。 執行DFS.

(4)輸出深度優先遍歷獲得的每一個樹的節點, 這就是我們的強連通分量的節點集合。

下面, 我們舉一個例子:

<span style="font-size:14px;">#include <iostream>
#include <list>
#include <stack>

using namespace std;

class Graph{
    int V; // number of vertices
    list<int> *adj; //  array of adjacency list

    // fills stack with vertices(in increasing oder of finishing time)
    // the top element of the stack has the maximum finishing time
    void fillOder(int v, bool visited[], stack<int>& Stack);

    // a recursive function to print DFS starting from v
    void DFSUtil(int v, bool visited[]);
public:
    Graph(int V);
    void addEdge(int v, int w);

    // the main function that finds and prints strongly connected component
    void printSCCs();

    // function that returns the transpose of the graph
    Graph getTranspose();
};

Graph::Graph(int V) {
    this -> V = V; // number of edges
    adj = new list<int>[V];
}

void Graph::DFSUtil(int v, bool visited[]) {
    visited[v] = true; // doing depth first search from v
    cout << v << " ";
    // recursion for all the vertices adjacent to v
    list<int>::iterator i;
    for(i = adj[v].begin(); i != adj[v].end(); ++i) {
        if(!visited[*i]) {
            DFSUtil(*i, visited);
        }
    }
}

Graph Graph::getTranspose() {
    Graph g(V);
    for(int v = 0; v < V; ++v) {
        // recursion for all the vertices adjacent to this vertex
        list<int>::iterator i;
        for(i = adj[v].begin(); i != adj[v].end(); ++i) {
           g.adj[*i].push_back(v);
        }
    }
    return g;
}

void Graph::addEdge(int v, int w) {
    adj[v].push_back(w); // add w to v's list
}

void Graph::fillOder(int v,  bool visited[], stack<int>& Stack) {
    // mark current node as visited and print it
    visited[v] = true;

    // recursions for all the vertices adjacent to the vertex v
    list<int>::iterator i;
    for(i = adj[v].begin(); i != adj[v].end(); ++i) {
        if(!visited[*i])
            fillOder(*i, visited, Stack);
    }

    // all vertices reachable from v are now processed and push it to the stack
    Stack.push(v);
}

void Graph::printSCCs() {
    stack<int> Stack;

    // mark all the vertices as not visited(for first DFS)
    bool *visited = new bool[V];
    for(int i = 0; i < V; ++i) {
        visited[i] = false;
     }

     // fill vertices in stack according to their finishing time
     for(int i = 0; i < V; ++i) {
        if(visited[i] == false) {
            fillOder(i, visited, Stack);
        }
     }

     // create a reversed graph
     Graph gr = getTranspose();
     // mark all the vertices not visited(for second DFS)

    // Mark all the vertices as not visited (For second DFS)
    for(int i = 0; i < V; i++)
        visited[i] = false;

    // Now process all vertices in order defined by Stack
    while (Stack.empty() == false)
    {
        // Pop a vertex from stack
        int v = Stack.top();
        Stack.pop();

        // Print Strongly connected component of the popped vertex
        if (visited[v] == false)
        {
            gr.DFSUtil(v, visited);
            cout << endl;
        }
    }
}

// Driver program to test above functions
int main()
{
    // Create a graph given in the above diagram
    Graph g(5);
    g.addEdge(1, 0);
    g.addEdge(0, 2);
    g.addEdge(2, 1);
    g.addEdge(0, 3);
    g.addEdge(3, 4);

    cout << "Following are strongly connected components in given graph \n";
    g.printSCCs();

    return 0;
}
</span>
運行結果如下所示:

介紹完上述問題後, 我們看一個強連通分量的經典題目King's Queen:

Description

Once upon a time there lived a king and he had N sons. And there were N beautiful girls in the kingdom and the king knew about each of his sons which of those girls he did like. The sons of the king were young and light-headed, so it was possible for one son to like several girls. 

So the king asked his wizard to find for each of his sons the girl he liked, so that he could marry her. And the king's wizard did it -- for each son the girl that he could marry was chosen, so that he liked this girl and, of course, each beautiful girl had to marry only one of the king's sons. 

However, the king looked at the list and said: "I like the list you have made, but I am not completely satisfied. For each son I would like to know all the girls that he can marry. Of course, after he marries any of those girls, for each other son you must still be able to choose the girl he likes to marry." 

The problem the king wanted the wizard to solve had become too hard for him. You must save wizard's head by solving this problem. 

Input

The first line of the input contains N -- the number of king's sons (1 <= N <= 2000). Next N lines for each of king's sons contain the list of the girls he likes: first Ki -- the number of those girls, and then Ki different integer numbers, ranging from 1 to N denoting the girls. The sum of all Ki does not exceed 200000(2000 * 100). 

The last line of the case contains the original list the wizard had made -- N different integer numbers: for each son the number of the girl he would marry in compliance with this list. It is guaranteed that the list is correct, that is, each son likes the girl he must marry according to this list. 

Output

Output N lines.For each king's son first print Li -- the number of different girls he likes and can marry so that after his marriage it is possible to marry each of the other king's sons. After that print Li different integer numbers denoting those girls, in ascending order.

Sample Input

4
2 1 2
2 1 2
2 2 3
2 3 4
1 2 3 4

Sample Output

2 1 2
2 1 2
1 3
1 4

大致意思是N個男的(編號是1, ... N), N個女的。 男的比較花心, 可以喜歡多個女的。 但是最後結婚的時候, 必須是隻能從他喜歡的女子中選一個, 並且還有保證自己選擇不會使得其他的男的無喜歡的女子可選, 這樣就皆大歡喜了。 一旦N個男的都有了自己喜歡的女的去結婚, 那麼就固定下來了。 但是我們不是給出一個配對方案, 而是給出所有讓人皆大歡喜的配對方案。 夠亂的。

其實這是一道考察了二部圖 + 最強連通分量的混合題。

關鍵是求最強連通分量。

首先第一步就是建圖。

N個男的, N個女的, 就相當於大小2N個頂點的頂點集合。 組成L, R的二部圖。  接下來就是建造邊了。

我們假設:

如果A(男的)喜歡B(女的), 就連一條從A到B的有向邊(A, B)。

根據巫師給出的list, 如果B女嫁給了A, 我們就建一條從B到A的邊。 

對於上述的sampe input建的圖如下:


然後求出所建圖的最強連通分量。 位於同一個最強連通分量的頂點可以自由混搭。 例如, 王子1, 王子2 在一個最強連通分量中, 表示這個強連通分量的女的都是兩個人喜歡的女的。 當然王子1隨便選一個女的結婚, 也不會造成與其處於同一個強連通分量的王子無別的女的可選。

相關程序如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=20000 + 100; // 200000邊的最大可能數
const int N=4200; // 2000 * 2 + 200 表示最多2000個男的, 2000個女的

struct node {
    int v;
    int next;
}edge[M];

// head[N]記錄的是N個女子
int head[N], num; // num 記錄的是邊的個數
bool vis[N],instack[N];
int stack[10*N],belong[N],top,bcnt,dindex,dep[N],low[N];
int n,m;

void init() {
    for(int i=0; i <= 2*n+5; i++) // 因爲總共有2n個節點
        head[i]=-1;
    num=0; // 記錄邊的個數, 給邊進行編號
}

void addege(int u,int v) { // 添加邊(u, v)
    edge[num].v=v; // 邊num 的終點
    edge[num].next=head[u]; // 起點爲u, -1表示沒有下一條邊, 1 表示有下一條邊
    head[u]=num++; //head[u]記錄下起點爲u上的邊數目
}

// 從節點i開始進行DFS
void dfs(int i) {
    int j, v;
    dep[i]=low[i]=++dindex; // 表示搜索時第i個頂點的訪問時間
    instack[i]=1; // 把第i個頂點入棧
    stack[++top]=i;  //
    for(j=head[i];j!=-1;j=edge[j].next) {
        v=edge[j].v;
        if(!dep[v]) {
            dfs(v);
            if(low[v]<low[i])
                low[i]=low[v];
        }
        else if(instack[v] && dep[v]<low[i]) // 只有強連通分量的根節點的dep才能與low
            low[i]=dep[v];
    }
    if(dep[i]==low[i]) { // 連通分量的根節點
        bcnt++;
        do
        {
            j=stack[top--];
            instack[j]=0;
            belong[j]=bcnt;
        }while(j!=i);
    }
}

void tarjan() {
    int i;
    top=bcnt=dindex=0;
    memset(dep,0,sizeof(dep));
    memset(vis,0,sizeof(vis));
    for(i=1;i<=n;i++)
        if(!dep[i]) {
            dfs(i);
        }
}

int rec[N];
void solve() {
    int i,j,cnt;
    for(i=1;i<=n;i++) {
        cnt=0;
        for(j=head[i];j!=-1;j=edge[j].next)
            if(belong[edge[j].v]==belong[i])
                rec[cnt++]=edge[j].v-n;
        sort(rec,rec+cnt);
        printf("%d",cnt);
        for(j=0;j<cnt;j++)
            printf(" %d",rec[j]);
        printf("\n");
    }
}
int main()
{
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);

    while((scanf("%d",&n))==1) {// 輸入有n個王子
        init();
        int a,j; // a對應的是王子喜歡的姑娘數, j 對應王子a喜歡的女子
        for(int i=1;i<=n;i++) { // 下標從1開始
            scanf("%d",&a);
            while(a--) { // 一直執行到a == 1即退出
                scanf("%d",&j);
                addege(i,j+n); // 連接王子i喜歡的姑娘之間的邊
            }
        }
        for(int i=1; i<=n; i++) { // 根據最後一行, 連接巫師給的完備匹配的邊
            scanf("%d",&j);
            addege(j+n,i);
        }
        tarjan();
        solve();
     }
    return 0;
}


運行結果:





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章