雙連通分量 總結及例題

點雙連通和邊雙連通

  • 連通的概念:在無向圖中,所有點能互相到達
  • 連通分量:互相聯通的子圖
  • 點雙連通:刪掉一個點之後,圖仍聯通
  • 邊雙連通:刪掉一條邊之後,圖仍聯通

tarjan 算法:

該算法是R.Tarjan發明的。對圖深度優先搜索, dfn[i]爲第i個結點在搜索樹中的深度,low[i]爲第i個結點的子樹的所有兒子連接到的最上面的結點層數。根據定義,則有:

Low(u)=Min
{
    dfn(u),
    dfn(v) ,(u,v)爲後向邊(返祖邊) 等價於 DFS(v)<DFS(u)且v不爲u的父親節點
    Low(v) ,(u,v)爲樹枝邊(父子邊)
}

一個頂點u是割點,當且僅當滿足(1)或(2)
(1) u爲樹根,且u有多於一個子樹。
(2) u不爲樹根,且滿足存在(u,v)爲樹枝邊(或稱父子邊,即u爲v在搜索樹中的父親),使得DFS(u)<=Low(v)。
一條無向邊(u,v)是橋,當且僅當(u,v)爲樹枝邊,且滿足DFS(u)

求雙連通分量

  1. 對於點雙連通分量,實際上在求割點的過程中就能順便把每個點雙連通分量求出。建立一個棧,存儲當前雙連通分量,在搜索圖時,每找到一條樹枝邊或後向邊(非橫叉邊),就把這條邊加入棧中。如果遇到某時滿足DFS(u)<=Low(v),說明u是一個割點,同時把邊從棧頂一個個取出,直到遇到了邊(u,v),取出的這些邊與其關聯的點,組成一個點雙連通分支。割點可以屬於多個點雙連通分量,其餘點和每條邊只屬於且屬於一個點雙連通分量支。
    (這種還沒有實現過,不過我認爲顯然如果把割點標記出來,跑dfs也能求出點雙連通分支,雖然代碼量會上升,不過還挺好打的,下面例題二類似)
  2. 對於邊雙連通分量,求法更爲簡單。只需在求出所有的橋以後,把橋邊刪除,原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分量。橋不屬於任何一個邊雙連通分量,其餘的邊和每個頂點都屬於且只屬於一個邊雙連通分量。

構造雙連通圖

  1. 一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖?方法爲首先求出所有的橋,然後刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮爲一個頂點,再把橋邊加回來,最後的這個圖一定是一棵樹,邊連通度爲1
  2. 統計出樹中度爲1的節點的個數,即爲葉節點的個數,記爲leaf。則至少在樹上添加(leaf+1)/2條邊,就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。具體方法爲,首先把兩個最近公共祖先最遠的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因爲一個形成的環一定是雙連通的。然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。

模板

int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector<pair<int, int>>bridge;

void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]); //返祖邊
        if (vis[v]==0)
        {
            dfs(v, u);
            son++;
            low[u]=min(low[u], low[v]);
            if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]))
            {
                cut[u]=true;
                //if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是橋
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    cnt=0; root=1;
    //bridge.clear();
}

例題

POJ1144【基礎】
題目大意:
給出一個無向圖,求出有多少個割點。
輸入:
有若干組測試數據。每一組測試數據的第一行有一個整數 n,表示有 n
(1<=n<100)個點,n=0 時測試數據結束。接下來有若干行,每一行第一個整
數 u 表示這一行描述的是以 u 爲起點的邊,接下來有若干個整數 vi 表示有一條
邊 u-vi,u=0 時表示這一組測試數據結束。
輸出:
對於每一組測試數據,輸出一個整數,即有多少個割點。

模板題,沒太多要說的

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=110;
vector<int> G[maxn], a;
char s[1000];
void add(int u, int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}

void deal()
{
    a.clear();
    int lens=strlen(s), now=0;
    for (int i=0; i<lens; i++)
        if (s[i]==' ')
        {
            a.push_back(now);
            now=0;
        }else now=now*10+(s[i]-'0');
    a.push_back(now);
}

//tarjan
int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector<pair<int, int>>bridge;

void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]);
        if (vis[v]==0)
        {
            dfs(v, u);
            son++;
            low[u]=min(low[u], low[v]);
            if ( ((u==root) && son>1) || (u!=root && low[v]>=dfn[u]))
            {
                cut[u]=true;
                //if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是橋
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    cnt=0; root=1;
    //bridge.clear();
}

int main()
{
    int n;
    while (scanf("%d\n", &n)!=EOF)
    {
        if (n==0) break;
        for (int i=1; i<=n; i++) G[i].clear();
        int u, v;
        while (1)
        {
            scanf("%[^\n]s", s); getchar();
            deal(); 
            for (int i=1; i<a.size(); i++) add(a[0], a[i]);
            if (a[0]==0) break; 
        }

        tarjan_init();
        dfs(1, -1);

        int ans=0;
        for (int i=1; i<=n; i++)
            if (cut[i]) ans++;
        printf("%d\n", ans);
    }
    return 0;
}

POJ1523

題目大意:
給出一個無向圖,求出其割點的數量,並且求出去掉每一個割點後原圖分成多
少個連通分量。
輸入:
有若干組測試數據。每一組測試數據有若干行,每一行有兩個整數表示無向圖
中的一條邊,或者一個 0 表示這一組測試數據的結束。所有測試數據最後以一
個 0 作爲結束。無向圖中的點的數量不超過 1000 個,假定一個圖裏面的點從 1
開始編號,並且是連續遞增的。
輸出:
對於每一組測試數據,如果有割點,按如下格式輸出:
Network #測試數據組序號
SPF node 割點 1 編號 leaves x1 subnets
SPF node 割點 2 編號 leaves x2 subnets
……
其中 xi 表示在圖中去掉某一個割點後產生的連通分量數量。
如果沒有割點,輸出:
Network #測試數據組序號
No SPF nodes
兩組輸出之間需要有一個空行進行分割。

題解:
先求割點,然後枚舉每一個割點裸 dfs 求連通分量數量。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=1010;
vector<int> G[maxn+10];
void add(int u, int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}

//tarjan
int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector<pair<int, int>>bridge;

void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]);
        if (vis[v]==0)
        {
            dfs(v, u);
            son++;
            low[u]=min(low[u], low[v]);
            if ( ((u==root) && son>1) || (u!=root && low[v]>=dfn[u]))
            {
                cut[u]=true;
                //if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是橋
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    cnt=0; root=1;
    //bridge.clear();
}
int n;
int belong[maxn], cnt2;

void dfs2(int u, int fa, int x)
{
    if (belong[u]!=-1) return; 
    belong[u]=cnt2;
    for (int i=0; i<(int)G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa || v==x) continue;
        dfs2(v, u, x);
    }
}

void deal(int x)
{
    memset(belong, -1, sizeof(belong));
    cnt2=0;
    for (int i=1; i<=n; i++) 
        if (i!=x && belong[i]==-1) ++cnt2, dfs2(i, -1, x);
}


int main()
{
    int a, b, kase=0;
    while (scanf("%d", &a)!=EOF)
    {
        if (a==0) break;
        kase++;
        printf("Network #%d\n", kase);
        for (int i=1; i<=1000; i++) G[i].clear();
        scanf("%d", &b);
        n=max(a, b);
        add(a, b);
        int u, v;
        while (scanf("%d", &u)!=EOF)
        {
            if (u==0) break;
            scanf("%d", &v);
            add(u, v);
            n=max(n, u); n=max(n, v);
        }
        tarjan_init();
        dfs(1, -1);
        bool ans=false;
        for (int i=1; i<=n; i++) if (cut[i]) ans=true; 
        if (ans==false)
            printf("  No SPF nodes\n");
        else
            for (int i=1; i<=n; i++)
            {
                deal(i);
                if (cut[i]) printf("  SPF node %d leaves %d subnets\n", i, cnt2);
            }
        printf("\n");
    }
    return 0;
}

POJ3694

題目大意:
給出一個無向圖,有 N 個點和 M 條邊( 1<=N<=100000, N-1<=M<=200000)求出
有多少條割邊(橋),以及每加入一條新的邊以後還剩下多少條割邊。假定所
有的點從 1 到 N 進行編號,一開始的時候全圖是連通的。
輸入:
有若干組測試數據。每一組測試數據的第一行有兩個整數 N 和 M,當 N=M=0 時
輸入數據結束。接下來有 M 行,每一行有兩個整數 u, v,表示在 u, v 之間有
一條邊。接下來有一行,包含一個整數 Q,表示將加入多少條邊。接下來有 Q
行,每一行按上述相同方式描述一條邊。
輸出:
對於每一組測試數據,先輸出一行“ Case %測試數據組編號%:”,然後對每一
條新加入的邊,輸出一行,包含一個整數,即加入了這一條邊以後還有多少條
割邊。

問題分析:
如下圖,如若加了綠邊,帶來的影響是從綠邊(u,v)往上的節點,直到lca(u,v)都不再是割點
這裏寫圖片描述
用並查集處理即可,這裏的lca不用寫O(log n)的版本,O(n)的即可

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=100100;
vector<int> G[maxn+10];
void add(int u, int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}
//tarjan
int root, cnt, ans;
int vis[maxn], dfn[maxn], low[maxn];
bool bridge[maxn];
int parent[maxn];
void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<(int)G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]); 
        if (vis[v]==0)
        {
            dfs(v, u);
            parent[v]=u;
            son++;
            low[u]=min(low[u], low[v]);
            if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]) )
            {
                ans++;
                bridge[v]=1;
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(bridge, 0, sizeof(bridge));
    cnt=0; root=1; ans=0;
}


void lca(int u, int v)
{
    if (dfn[v]<dfn[u]) swap(u, v);
    while (dfn[v]>dfn[u])
    {
        if(bridge[v]) {
            ans--;
            bridge[v] = 0;
        }
        v=parent[v];
    }
    while(u!= v)
    {
        if (bridge[u]) {
            ans--;
            bridge[u]=0;
        }
        u=parent[u]; v=parent[v];
    }
}

int main()
{
    int n, m, kase=0;
    while (scanf("%d%d", &n, &m)!=EOF)
    {
        if (n==0 && m==0) break;
        printf("Case %d:\n", ++kase);
        for (int i=1; i<=m; i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u, v);
        }
        tarjan_init();
        dfs(1, -1);
        int Q;
        scanf("%d", &Q);
        while (Q--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            lca(u, v);
            printf("%d\n", ans);
        }
        printf("\n");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章