『Tarjan』Tarjan求強連通分量模板


學習Tarjan前提須知

Tarjan是一個能夠求強連通分量的算法。何為強聯通?就是在一個圖中,兩點可以相互到達從而形成的一個環,我們稱這個環為強聯通,其中,在這個圖中所能組成點最多的環,我們稱它為強連通分量,而我們的Tarjan就能求強聯通與強聯通分量 甚至能進行縮點等一系列操作

算法內容

競賽需要用到的點

1、Tarjan求出強聯通后自由度很高,建議不要和強連通分量綁在一起

2、Tarjan較為常見,考慮可以組成一套模型來使用

Tarjan求強聯通分量略講

本人對Tarjan的low算法理解不深,也沒有哪個博客作出具體的解釋,這里就引用wiki了

Tarjan算法 wiki

Tarjan 算法

Robert E. Tarjan (1948~) 美國人。

Tarjan 發明了很多算法結構。光 Tarjan 算法就有很多,比如求各種連通分量的 Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法。並查集、Splay、Toptree 也是 Tarjan 發明的。

我們這里要介紹的是在有向圖中求強連通分量的 Tarjan 算法。

另外,Tarjan 的名字 j 不發音,中文譯為塔揚。

DFS 生成樹

在介紹該算法之前,先來了解 DFS 生成樹 ,我們以下面的有向圖為例:

有向圖的 DFS 生成樹主要有 4 種邊(不一定全部出現):

  1. 樹邊(tree edge):綠色邊,每次搜索找到一個還沒有訪問過的結點的時候就形成了一條樹邊。
  2. 反祖邊(back edge):黃色邊,也被叫做回邊,即指向祖先結點的邊。
  3. 橫叉邊(cross edge):紅色邊,它主要是在搜索的時候遇到了一個已經訪問過的結點,但是這個結點 並不是 當前結點的祖先時形成的。
  4. 前向邊(forward edge):藍色邊,它是在搜索的時候遇到子樹中的結點的時候形成的。

我們考慮 DFS 生成樹與強連通分量之間的關系。

如果結點 \(u\) 是某個強連通分量在搜索樹中遇到的第一個結點,那么這個強連通分量的其余結點肯定是在搜索樹中以 \(u\) 為根的子樹中。\(u\) 被稱為這個強連通分量的根。

反證法:假設有個結點 \(v\) 在該強連通分量中但是不在以 \(u\) 為根的子樹中,那么 \(v\)\(u\) 的路徑中肯定有一條離開子樹的邊。但是這樣的邊只可能是橫叉邊或者反祖邊,然而這兩條邊都要求指向的結點已經被訪問過了,這就和 \(u\) 是第一個訪問的結點矛盾了。得證。

Tarjan 算法求強連通分量

在 Tarjan 算法中為每個結點 \(u\) 維護了以下幾個變量:

  1. \(dfn[n]\) :深度優先搜索遍歷時結點 u 被搜索的次序。
  2. \(low[u]\) :設以 u 為根的子樹為 \(Subtree(u)\)\(low[u]\) 定義為以下結點的 \(dfn\) 的最小值: \(Subtree(u)\) 中的結點;從 \(Subtree(u)\) 通過一條不在搜索樹上的邊能到達的結點。

一個結點的子樹內結點的 dfn 都大於該結點的 dfn。

從根開始的一條路徑上的 dfn 嚴格遞增,low 嚴格非降。

按照深度優先搜索算法搜索的次序對圖中所有的結點進行搜索。在搜索過程中,對於結點 和與其相鄰的結點 \(v\) (v 不是 u 的父節點)考慮 3 種情況:

  1. \(v\) 未被訪問:繼續對 v 進行深度搜索。在回溯過程中,用 \(low[v]\) 更新 \(low[u]\) 。因為存在從 \(u\)\(v\) 的直接路徑,所以 \(v\) 能夠回溯到的已經在棧中的結點,\(u\) 也一定能夠回溯到。
  2. \(v\) 被訪問過,已經在棧中:即已經被訪問過,根據 \(low\) 值的定義(能夠回溯到的最早的已經在棧中的結點),則用 \(dfn[v]\) 更新 \(low[u]\)
  3. \(v\) 被訪問過,已不在在棧中:說明 \(v\) 已搜索完畢,其所在連通分量已被處理,所以不用對其做操作。

對於一個連通分量圖,我們很容易想到,在該連通圖中有且僅有一個 \(dfn[u] = low[u]\) 。該結點一定是在深度遍歷的過程中,該連通分量中第一個被訪問過的結點,因為它的 DFN 值和 LOW 值最小,不會被該連通分量中的其他結點所影響。

因此,在回溯的過程中,判定 \(dfn[u] = low[u]\) 的條件是否成立,如果成立,則棧中從 \(u\) 后面的結點構成一個 SCC。

實現代碼如下 參考LuoGuP2341強連通分量模板

[此代碼未編譯 可能會有問題 請斟酌參考]

//#define fre yes

#include <cstdio>
#include <cstring>
#include <iostream>

const int N = 50005;
int low[N], dfn[N];
int head[N << 1], to[N << 1], ver[N << 1];
int color[N], Stack[N], de[N];
bool Vis[N];

int tot;
void addedge(int x, int y) {
    ver[tot] = y;
    to[tot] = head[x];
    head[x] = tot++;
}

int num, top, col;
void tarjan(int x) {
    dfn[x] = low[x] = ++num;
    Stack[++top] = x;
    Vis[x] = 1;
    for (int i = head[x]; ~i; i = to[i]) {
        int v = ver[i];
        if(!dfn[v]) {
            tarjan(v);
            low[x] = std::min(low[x], low[v]);
        } else if(Vis[v]) {
            low[x] = std::min(low[x], dfn[v]);
        }
    }
    
    if(dfn[x] == low[x]) {
        ++col;
        color[x] = col;
        Vis[x] = 0;
        while(Stack[top] != x) {
            color[Stack[top]] = col;
            Vis[Stack[top--]] = 0;
        } top--;
    }
}

int main() {
    memset(head, -1, sizeof(head));
    static int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        addedge(u, v);
    }
    
    for (int i = 1; i <= n; i++) {
        if(!dfn[i]) {
			tarjan(i);
        }
    }
    
    for (int i = 1; i <= n; i++) {
        for (int j = head[i]; ~j; j = to[j]) {
            int v = ver[j];
            if(color[i] != color[v]) {
                de[color[i]]++;
            }
        }
    }

    int ans = 0, u = 0, k = 0;
    for (int i = 1; i <= col; i++) {
        if(!de[i]) {
            u++;
            k = i;
        }
    }

    if(u == 1) {
        for (int i = 1; i <= n; i++) {
            if(color[i] == k) ans++;
        } printf("%d\n", ans);
    } else puts("0");
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM