學習筆記----圖論學習中

畫船聽雨發表於2014-01-10

現在在外邊實訓,事情不是很多,趕緊補一下圖論,發現自己已經落下太多的東西了啊、、、自己已經越來越差勁了啊,必須讓自己多學點啊。。。沒帶紙筆只好現在部落格裡記錄一下學習的內容,回去之後再好好的整理一下啊。

前幾天再跟著老師瞎鼓搗那個所謂的專案,現在專案做了一半了,整體已經差不多也都可以了,所以看看書。

按時間寫吧,以後也好整理:

1.10;(總結來自劉汝佳寫的大白書)還要感謝老譚的指導。

今天看的東西也不是很多,就是看了一下無向圖的割頂和橋:

首先寫一下割點的定義:就是在一個圖中如果刪除掉某些點之後會使得圖的連通分量增加,簡單的說就是以前是n個迴路,先變成n+1,或者是等多的迴路了。


最簡單的判斷一個點是不是割點的方法就是把所有的點進行dfs,然後看是否會有連通分量增加,這樣是很慢的,書上說會是O(n*(n+m))的時間複雜度,於是就有了一種更優化的演算法。

先提出了時間戳的概念:就是到達這一步的時間是多少,其實並不是真的多少分鐘多少秒,而是到達這步是第幾步了。

這種演算法的簡述就是:先求出到達這一步的時間,然後從這個點之後所有子樹中(就是說這個點之後所有可以到達這個點的點)求出到達他們之間到達的最少的時間,然後比較最少時間low和pre[u],如果low < pre[u],就是說u點之後的點通過其他的祖先可以比通過u更快的到達,那麼這就說明u點之後的某個點不通過u也可以直接到達,那麼u點就不是割點了;反之如果low >= pre[u],那就是除了這一點之後的點不能通過其他點直接到達,那麼這個點就是割點了啊,因為刪掉它之後就會導致連通分量增加。

下面附上通過劉汝佳的程式碼寫的程式碼,其實主要是老譚同學寫的我學習了一下啊。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <stack>
#include <vector>
#include <queue>
#include <map>

using namespace std;

const int maxn = 1000;

vector<int> G[maxn];
int pre[maxn], dfs_clock, low[maxn], n;
bool iscut[maxn];

void init()
{
    for(int i=0; i<n; i++) G[i].clear();
    memset(iscut, false, sizeof(iscut));
    memset(pre, 0, sizeof(pre));
    dfs_clock = 0;
}

int dfs(int u, int fa)  //u在DFS樹中的父結點是fa
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0; //子結點數目
    for(int i=0; i<G[u].size(); i++)
    {
        int v = G[u][i];
        if(!pre[v])     //沒有訪問過v, 沒有必要用vis標記了
        {
            child++;
            int lowv = dfs(v, u);
            lowu = min(lowu, lowv); //用後代的 low 函式更新 u 的 low 函式
            if(lowv >= pre[u])
            {
                iscut[u] = true;
            }
        }
        else if(pre[v] < pre[u] && v != fa)  //(u,v)為反向邊
        {
            lowu = min(lowu, pre[v]);   //用反向邊更新 u 的 low 函式
        }
        if(fa < 0 && child == 1) iscut[u] = false;
    }
    low[u] = lowu;
    return lowu;
}

int main()
{
    int m, u, v;
    scanf("%d%d", &n, &m);

    init();

    for(int i=0; i<m; i++)
    {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    for(int i=0; i<n; i++)
        if(!pre[i])
        {
            dfs(i, -1);
        }

    for(int i=0; i<n; i++)  //將割點輸出
    {
        if(iscut[i]) printf("%d ", i);
    }
    putchar('\n');

    return 0;
}

實訓終於結束了啊,又在那裡不幸感冒發了小燒,一直沒怎麼再看書,所以沒寫,回來的補上啊。

下面是無向圖的點雙連通分量:

先摘劉汝佳大白書上的一段話吧:對於一個連通圖,如果任意兩點至少存在兩條“點不重複”的路徑,(這裡得說一下,一定注意要點不重複,這裡是為什麼要求出割點的原因,因為割點把它們分成了不同的塊)則說明這個圖是點聯通圖,即內部無割頂。

        類似的,如果任意兩點至少存在兩條“邊不重複”的路徑,我們說這個圖是邊雙連通的。這個要求低一點,只需要每條邊至少在一個簡單環中,即所有邊都不是橋。

       對於一張無向圖,點-雙連通圖,點-雙連通圖的極大子圖成為雙連通分量或塊。每條邊恰好屬於一個雙連通分量,但不同雙連通分量可能會有公共點。可以證明不同雙連通分量最多隻有一個公共點,而且一定是割頂。另一方面,任意割頂都是至少兩個不同雙連通分量的公共點。

       同理邊-雙連通分量的橋與這裡的割頂是一樣的啊。

 ———————————————————————————————————————————————————

  在下面說一下我自己的理解: 這裡是點連通圖,兩個點之間要至少有兩條路可以到達,而且這兩條路上不可以有相同的點,這才是點雙連通圖。注意了這一點就很明瞭了啊,每個割點都是一個分割點,把圖分成幾部分。注意這裡的幾部分至少有一個是圖的塊,所以每個塊中也至少會包含一個割頂。因此要想找到塊就必須先找到割頂,然後根據割頂把圖分開,把割頂相連的所有的點加入到棧當中,然後判斷所有的點所屬於的塊集,如果已經屬於別的集合的點(一般是割頂),就不加入到容器中,用vector儲存點是第幾個塊的第幾個元素。注意當出棧時有可能棧不空但是已經將割頂彈出了,就退出迴圈,有可能棧不為空。


說明一下:在演算法中每個割頂屬於哪個塊是沒有意義的。

以POJ 3352 為例貼一下程式碼:

Road Construction
Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 7704   Accepted: 3874

Description

It's almost summer time, and that means that it's almost summer construction time! This year, the good people who are in charge of the roads on the tropical island paradise of Remote Island would like to repair and upgrade the various roads that lead between the various tourist attractions on the island.

The roads themselves are also rather interesting. Due to the strange customs of the island, the roads are arranged so that they never meet at intersections, but rather pass over or under each other using bridges and tunnels. In this way, each road runs between two specific tourist attractions, so that the tourists do not become irreparably lost.

Unfortunately, given the nature of the repairs and upgrades needed on each road, when the construction company works on a particular road, it is unusable in either direction. This could cause a problem if it becomes impossible to travel between two tourist attractions, even if the construction company works on only one road at any particular time.

So, the Road Department of Remote Island has decided to call upon your consulting services to help remedy this problem. It has been decided that new roads will have to be built between the various attractions in such a way that in the final configuration, if any one road is undergoing construction, it would still be possible to travel between any two tourist attractions using the remaining roads. Your task is to find the minimum number of new roads necessary.

Input

The first line of input will consist of positive integers n and r, separated by a space, where 3 ≤ n ≤ 1000 is the number of tourist attractions on the island, and 2 ≤ r ≤ 1000 is the number of roads. The tourist attractions are conveniently labelled from 1 to n. Each of the following r lines will consist of two integers, v and w, separated by a space, indicating that a road exists between the attractions labelled v and w. Note that you may travel in either direction down each road, and any pair of tourist attractions will have at most one road directly between them. Also, you are assured that in the current configuration, it is possible to travel between any two tourist attractions.

Output

One line, consisting of an integer, which gives the minimum number of roads that we need to add.

Sample Input

Sample Input 1
10 12
1 2
1 3
1 4
2 5
2 6
5 6
3 7
3 8
7 8
4 9
4 10
9 10

Sample Input 2
3 3
1 2
2 3
1 3

Sample Output

Output for Sample Input 1
2

Output for Sample Input 2
0
題目的大意就是:

在暑假裡景區要修路,為保障遊客可以正常的遊覽,所以要修的路不能是“橋”,否則回使圖不連通了,讓你求出這個圖再修之前至少先加入多少個點,使得這個圖可以保證這個圖是點-雙連通的。

解體的思路是:求出所有的塊,然後求出所有“塊”中只有一個割點的數目然後[cnt+1]/2就是要修的路的數目。

#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <iomanip>
#include <stdio.h>
#include <string>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define eps 1e-7
#define M 1001000
#define LL __int64
#define INF 0x3f3f3f3f
#define PI 3.1415926535898


using namespace std;
const int maxn = 1001000;

int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt, n;
vector<int> G[maxn], bcc[maxn];

struct Edge
{
    int u, v;
};

stack<Edge> S;
int dfs(int u, int fa)
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        Edge e;
        e.u = u;
        e.v = v;
        if(!pre[v])
        {
            S.push(e);
            child ++;
            int lowv = dfs(v, u);
            lowu = min(lowv, lowu);
            if(lowv >= pre[u])
            {
                iscut[u] = 1;
                bcc_cnt ++;
                bcc[bcc_cnt].clear();
                while(1)
                {
                    Edge x = S.top();
                    S.pop();
                    if(bccno[x.u] != bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.u);
                        bccno[x.u] = bcc_cnt;
                    }
                    if(bccno[x.v] != bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.v);
                        bccno[x.v] = bcc_cnt;
                    }
                    if(x.v == v && x.u == u)
                        break;
                }
            }
        }
        else if(pre[v] < pre[u] && v != fa)
        {
            S.push(e);
            lowu = min(lowu, pre[v]);
        }
    }
    if(fa < 0 && child == 1)
        iscut[u] = 0;
    return lowu;
}

void find_bcc()
{
    int i;
    memset(bccno , 0 , sizeof(bccno));
    memset(iscut , 0 , sizeof(iscut));
    memset(pre , 0 , sizeof(pre));
    bcc_cnt = 0;
    dfs_clock = 0;
    for(i = 1; i <= n; i++)
        if(!pre[i])
            dfs(i, -1);
}
int main()
{
    int u, v, m, i, j;
    while(cin >>n>>m)
    {
        for(i = 1; i <= m; i++)
        {
            cin >>u>>v;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        find_bcc();
        //cout <<bcc_cnt<<endl;
        int cnt = 0;
        int ant = 0;
        for(i = 1; i <= bcc_cnt; i++)
        {
            ant = 0;
            for(j = 0; j < bcc[i].size(); j++)
            {
                if(iscut[bcc[i][j]])
                    ant ++;
            }
            if(ant == 1)
                cnt++;
        }
        cout<<(cnt+1)/2<<endl;
    }
    return 0;
}

未完待續。。。。。



相關文章