1. 程式人生 > 其它 >演算法專題——雙連通分量

演算法專題——雙連通分量

雙連通分量

概念

也叫重連通分量.由於無向圖的連通更加容易, 所以比起有向圖中連通更加註重節點之間能否相互到達的特點, 無向圖的連通會更加註重塊與塊之間能否相互到達.於是也會更加註意一個連通塊中, 一些邊和點對於整個連通塊的影響.雙連通分量分為兩類.

  1. 邊雙連通分量, 對應的會影響分量的連通性的邊稱為割邊, 也叫橋. 為極大的不包含割邊的連通塊.
  2. 點雙連通分量, 對應的會影響分量的連通性的點稱為割點. 為極大的不包含割點的連通塊

點雙和邊雙並不會互通. 即一個分量是點雙不一定是邊雙, 反之亦然.

與強連通分量相似, 找雙連通分量通常也只是求解問題中的一個步驟, 縮點之後, 才是真正的開始.

不同於有向圖, 無向圖沒有橫叉邊

, 至於具體原因, 可以自己想想.



邊雙的性質

  1. 邊雙圖中任意兩點之間至少存在兩條邊不重合的路徑.(邊雙同有向圖的強連通分量一樣, 可以看作一個環的耦合連線)
  2. 非割邊的邊僅屬於一個邊雙, 割邊不屬於任意一個邊雙.
  3. 邊雙縮點之後會得到一棵樹.

點雙的性質

  1. 除去一種特殊的點雙外, 其他的點雙圖中任意兩點之間至少存在兩條點不重合的路徑.
  2. 非割點的點僅屬於一個點雙, 割點至少屬於一個點雙.

找邊雙連通分量

邊雙連通分量的求解也可以使用Tarjan演算法實現, 由於·橫叉邊已不存在, 因此對於以訪問過的節點, 一定都仍然儲存於dfs棧中(可以自己想想為啥, 和無向圖沒有橫叉邊的原因一致).

程式碼:

int dfn[MAXN], low[MAXN], timestamp, stk[MAXN], top;
int dcc_cnt, bridge, is_bridge[MAXN], id[MAXN], size[MAXN];
void Tarjan(int u, int pre) {
    dfn[u] = low[u] = ++timestamp;
    stk[++top] = u;
    int v;
    for (int i = h[u]; ~i; i = ne[i]) {
        v = e[i];
        if (!dfn[v]) {
            Tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if (dfn[u] < low[v]) {									//cal bridge
                bridge++;
                is_bridge[i] = is_bridge[i ^ 1] = true;
            }
        }
        else if (i != (pre ^ 1)) low[u] = min(low[u], dfn[v]);		//不為父節點
    }
    if (low[u] == dfn[u]) {
        dcc_cnt++;
        do {
            v = stk[top--];
            id[u] = dcc_cnt;										//coloring
            size[dcc_cnt]++;										//cal s
        } while (v != u);
    }
}

找點雙連通分量

仍然是Tarjan演算法, 但加了一些特判處理.

一個點是割點, 僅當其一個子節點不能到達該點的父節點(即dfn[u] <= low[v]), 且該節點至少連線兩個子樹時成立.對於非根節點的節點而言, 可以到達該點必然是通過父節點一側的子樹過來的, 再連線一個子節點一側的子樹即可, 而對於根節點而言, 則需要進行一個特判, 判斷其是否至少與兩個子樹相連.具體看程式碼.

一個點是不是割點需要進行以上的特判, 但一個點所連線的子樹是不是點雙, 則只要dfn[u] <= low[v]成立即可.具體看程式碼.

程式碼:

int dfn[MAXN], low[MAXN], timestamp;
int stk[MAXN], top;
int dcc_cnt, cut[MAXN];
vector<int> dcc[MAXN];

void Tarjan(int u) {
    dfn[u] = low[u] = ++timestamp;
    stk[++top] = u;
    if (u == root && h[u] == -1) {		//一個點也是點雙
        dcc_cnt++;
        dcc[dcc_cnt].push_back(u);
        return ;
    }
    int cnt = 0, v;						//記錄
    for (int i = h[u]; ~i; i = ne[i]) {
        v = e[i];
        if (!dfn[v]) {
            Tarjan(v);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                cnt++;										//is a subtree
                if (u != root || cnt > 1) cut[u] = true;	//cutJudge
                dcc_cnt++;
                int y;
                do {
                    y = stk[top--];
                    dcc[dcc_cnt].push_back(y);
                } while (y != v);
                dcc[dcc_cnt].push_back(u);					//get dcc
            }
        }
        else low[u] = min(low[u], low[v]);					//no effect
    }
}

例題

Redundant Paths

題面:

分析:

給出一個無向圖, 求解需要新增多少條邊可以將原圖變成一個邊雙.

直接使用二級結論:縮點之後得到一顆樹, 需要新增的邊數即為(葉子節點數 + 1) / 2, 即邊數的一半向上取整, 下面給出證明.

我們知道, 一個簡單的邊雙就是一個一個環,一個複雜的邊雙就是多個環進行的耦合連線, 因此得到一顆樹之後, 我們的任務就是要連線最少的邊將原圖連線成一個環. 而環的性質在於沒有一個點的度為1, 所有點的度都大於1. 而對於一顆樹而言, 其擁有葉子節點個數個度為1的點, 所以, 一種樸素的想法就是通過連線一條邊, 減少樹中的葉子節點, 減少度為1的點, 最為理想的情況連線兩個葉子節點可以消去兩個度為1的點, 但是在消去兩個葉子節點的同時, 還要防止新的葉子節點生成(新的葉子節點只由縮點之後的得到的點產生), 因此選擇哪兩個葉子節點進行連線非常重要. 連線兩個節點之後會進行縮點從而再進行下一步的連線, 縮點的物件是以兩個葉子節點為端點的一條鏈, 縮點之後將其變為一個點, 而為了防止該點變成葉子節點, 選擇的兩個葉子節點對應的鏈上至少與其他兩條鏈相連. 而在連線的過程中, 總是可以找到這樣的兩個葉子節點, 使其對應的鏈滿足條件. 因此只需要連線(葉子節點數 + 1) / 2條邊.

邊界條件: 葉子樹為1, 不需要加邊; 葉子數為2, 加一條邊; 葉子數為為3, 連線兩條邊.