1. 程式人生 > >如何用Tarjan求強連通分量——洛谷P2863題解

如何用Tarjan求強連通分量——洛谷P2863題解

truct inf ble image img 圖片 stream 初始 為我

強連通分量

強連通分量就是一個有向圖的子集,這個子集中的點任意兩點間一定可以互相到達。如下圖:
技術分享圖片
如圖上面的紅框中的點就組成了一個強連通分量。

如何求強連通分量:

首先我們要有兩個數組 dfn[]low[] 。其中 dfn[i] 表示第i個點的在第 dfn[i] 次被dfs到。然後 low[i] 存的是第 \(i\) 個點所在的連通塊裏面 dfn 的值最小的點。之後我們還需要一個臨時存放遍歷到但是還沒找到其所屬的強連通分量的點。然後我們就可以開始我們的算法流程了。

然後上代碼:

void Tarjan(int x, int index) {
    dfn[x] = low[x] = index; //初始化dfn, low
    stack_tmp[++tail] = x;   //將當前結點放入棧中
    check[x] = true;             //標記結點x表示x在棧中
    for (register int i = head[x]; i; i = Edge[i].nxt) {
        if (!dfn[Edge[i].e]) Tarjan(Edge[i].e, index + 1);
        low[x] = Min(low[x], low[Edge[i].e]); //更新low值
    }
    if (low[x] == dfn[x]) {
        int sum = 0; //記錄連通塊內結點的數量
        while (stack_tmp[tail] != x && tail > 0) {
            check[stack_tmp[tail]] = false; //取消標記
            ++sum;
            --tail; //退棧
        }
        if (tail != 0 && stack_tmp[tail] == x) { //將當前結點退棧
            check[stack_tmp[tail]] = false;
            ++sum;
            --tail;
        }
    }
}

也許有人會對下面這段代碼不是很理解。

 for (register int i = head[x]; i; i = Edge[i].nxt) {
        if (!dfn[Edge[i].e]) Tarjan(Edge[i].e, index + 1);
        low[x] = Min(low[x], low[Edge[i].e]); //更新low值
    }

這樣更新 low值的原因是假如當前結點沒有被訪問過,那麽我們首先一定是要訪問這個結點所連向的邊,因為我們的 low[x] 的值是從點 \(x\) 連出去的點的 low值更新來的,所以我們要先 dfs 連出去的點,更新連出去的點的 low

值,直到遍歷到的點沒有連出去的點的時候,那麽就說明這個點肯定自己就是一個強連通分量(因為我們不可能通過任何路徑回到這個點)。所以不用更新 low 值,直接返回。然後我們就要更新當前結點的 low 值了。然後我們更新當前結點的 low 值就可以了。之後假如 low[x] == dfn[x] 那麽說明我們成功找到一個強連通分量,然後將棧中的結點彈出直到將當前結點 \(x\) 彈出為止。

模板題:

Luogu P2863

代碼:

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5;
const int M = 5e4 + 5;

int n, m;
int u, v;
int head[N];
int cnt = 0;
int ans = 0;
int dfn[N], low[N];
int stack_tmp[N], tail = 0;
bool check[N];

inline int Min(int x, int y) {
    return x <= y ? x : y;
}

struct EDGE {
    int s;
    int e;
    int nxt;
} Edge[N];

void add(int u, int v) {
    ++cnt;
    Edge[cnt].s = u;
    Edge[cnt].e = v;
    Edge[cnt].nxt = head[u];
    head[u] = cnt;
}

void Tarjan(int x, int index) {
    dfn[x] = low[x] = index;
    stack_tmp[++tail] = x;
    check[x] = true;
    for (register int i = head[x]; i; i = Edge[i].nxt) {
        if (!dfn[Edge[i].e]) Tarjan(Edge[i].e, index + 1);
        low[x] = Min(low[x], low[Edge[i].e]);
    }
    if (low[x] == dfn[x]) {
        int sum = 0;    
        while (stack_tmp[tail] != x && tail > 0) {
            check[stack_tmp[tail]] = false;
            ++sum;
            --tail;
        }
        if (tail != 0 && stack_tmp[tail] == x) {
            check[stack_tmp[tail]] = false;
            ++sum;
            --tail;
        }
        if (sum > 1) ++ans;
    }
}

int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * w;
}

void write(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

int main(int argc, char const *argv[]) {
    n = read(), m = read();
    for (register int i = 1; i <= m; ++i) {
        u = read(), v = read();
        add(u, v);
    }
    Tarjan(1, 1);
    for (register int i = 1; i <= n; ++i) 
        if (!dfn[i]) Tarjan(i, 1);
    write(ans), putchar('\n');
    return 0;
}

如何用Tarjan求強連通分量——洛谷P2863題解