Tarjan 的各种算法

255 阅读3分钟

离线 LCA

#include<bits/stdc++.h>
using namespace std;

// divide the vertex into three category
// 1. visited
// 2. visiting
// 3. not visit
enum class Status {
    NOT_VISIT,
    VISITING,
    VISITED,
};

const int MAX_N = 10010, MAX_M = 20010;

int N, M;
// graph
int h[MAX_N], to[MAX_N * 2], wt[MAX_N * 2], nx[MAX_N * 2], id;
// union-find-set
int fa[MAX_N];
Status st[MAX_N];
vector<pair<int, int>> query[MAX_N];
pair<int, int> src_query[MAX_M];
int lca_list[MAX_M];

void add (int u, int v, int w) {
    to[id]= v, wt[id] = w, nx[id] = h[u], h[u] = id++;
}

int fnd (int x) {
    if (fa[x] != x) fa[x] = fnd(fa[x]);
    return fa[x];
}

// for each visiting vertex, check which query is related to it
// if the other vertex in this query is visited, then the lca is
// the father of the other vertex in union-find-set
//
// The key concept of tarjan algorithm is to group 
// the visiting vertex and it's visited subtree as an union-find-set
void tarjan (int u) {
    st[u] = Status::VISITING;

    for (int i = h[u]; ~i; i = nx[i]) {
        int v = to[i];
        if (st[v] == Status::NOT_VISIT) {
            tarjan(v);
            // key step !!!
            // must be set after tarjan(v), cannot be set before tarjan(v).
            // Otherwise, consider situation like 'a'->'b'->'c'->'d'
            // when 'd' is already visited, and trace back to visiting 'c',
            // and a query is about 'c'(visiting) and 'd'(visited),
            // this will cause to find 'a' as their lca,
            // which is not correct
            // Thus !!! the father of vertex v should not be set to u
            // util vertex v is visited.
            fa[v] = u;
        }
    }

    for (auto p: query[u]) {
        int v = p.first, query_id = p.second;
        if (st[v] == Status::VISITED) {
            // int lca = fnd(v);
            // lca_list[query_id] = u == v ? u : lca;

            // NOTE: It's impossible to have u == v and st[u] == Status::VISITED
            // the lca of u and v will not be calculated here
            // Thus, set the lca for u and v outside of tarjan algorithm
            lca_list[query_id] = fnd(v);
        }
    }

    st[u] = Status::VISITED;
}

void init () {
    memset(h, -1, sizeof h);
    for (int i = 0; i < MAX_N; i++) fa[i] = i;
}

int main () {
    init();

    cin >> N >> M;
    for (int i = 0; i < N - 1; i++) {
        int u, v, w; cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, w);
    }

    for (int i = 0; i < M; i++) {
        int x, y; cin >> x >> y;
        query[x].push_back({ y, i });
        query[y].push_back({ x, i });
        src_query[i] = {x, y};
        if (x == y) lca_list[i] = x;
    }

    tarjan(1);

    for (int i = 0; i < M; i++) {
        auto q = src_query[i];
        int x = q.first, y = q.second;
        printf("the lca of %d and %d is %d\n", x, y, lca_list[i]);
    }

    return 0;
}

SCC

#include<bits/stdc++.h>
using namespace std;

const int MAX_N = 10005, MAX_M = 50005;

int N, M;

int h[MAX_N], to[MAX_M], nx[MAX_M], e_id;
// NOTE: the actual definition of low[u] is
// the earliest vertext that u can reach without going through it's ancestor
// this definition will be useful when caculating cut vertex in undirected graph
int dfn[MAX_N], low[MAX_N], ts;

stack<int> stk;
bool in_stk[MAX_N];

// scc[u] denotes the scc that u belongs to
// dout[id] denotes how many out degree the scc id has
int scc[MAX_N], dout[MAX_N], scc_id;

// why we assign id from 0 rather than 1 ?
// because assign from 0 will take advantage in un-directed graph
// which is easier to calculate the reverse edge of i by doing
// i ^ 1, i.e, 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, ...
void add (int u, int v) {
    to[e_id] = v, nx[e_id] = h[u], h[u] = e_id++;
}

void tarjan_scc (int u) {
    dfn[u] = low[u] = ++ts;
    stk.push(u); in_stk[u] = true;

    for (int i = h[u]; ~i; i = nx[i]) {
        int v = to[i];
        if (!dfn[v]) {
            tarjan_scc(v);
            low[u] = min(low[u], low[v]);
        }
        // backward edge from u to the ancestor of u
        else if (in_stk[v]) {
            // according to the definition of low[u],
            // v is now the ancestor of u, low[v] might be smaller than dfn[v]
            // which means we will go through v, so we should only use dfn[v] to update low[u]
            low[u] = min(low[u], dfn[v]);
        }
    }

    if (dfn[u] == low[u]) {
        int v;
        scc_id++;
        do {
            v = stk.top();
            stk.pop();
            in_stk[v] = false;
            scc[v] = scc_id;
        } while (v != u);
    }
}

int main () {
    memset(h, -1, sizeof h);

    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int u, v; cin >> u >> v;
        add(u, v);
    }

    for (int u = 1; u <= N; u++) {
        if (!dfn[u]) {
            tarjan_scc(u);
        }
    }

    for (int u = 1; u <= N; u++) {
        for (int i = h[u]; ~i; i = nx[i]) {
            int v = to[i];

            int scc_u = scc[u], scc_v = scc[v];
            if (scc_u != scc_v) {
                dout[scc_u]++;
            }
        }
    }

    return 0;
}

E-BCC

桥 & E-BCC

#include<bits/stdc++.h>
using namespace std;

const int MAX_N = 10005, MAX_M = 50005;

int N, M;

int h[MAX_N], to[MAX_M], nx[2 * MAX_M], e_id;
int dfn[MAX_N], low[MAX_N], ts;

// base on the requirement
// stack is optional if we only need to calculate
// the bridge-edge or cut-vertex
stack<int> stk;

// ebcc[u] denotes the ebcc that u belongs to
// d[id] denotes how many degree the ebcc id has
int ebcc[MAX_N], d[MAX_N], ebcc_id;
bool is_bridge[MAX_M];

void add (int u, int v) {
    to[e_id] = v, nx[e_id] = h[u], h[u] = e_id++;
}

void tarjan_ebcc (int u, int from_e) {
    dfn[u] = low[u] = ++ts;
    stk.push(u);

    for (int i = h[u]; ~i; i = nx[i]) {
        int v = to[i];
        if (!dfn[v]) {
            tarjan_ebcc(v);
            low[u] = min(low[u], low[v]);
            // judge bridge here
            if (dfn[u] < low[v]) {
                is_bridge[i] = is_bridge[i ^ 1] = true;
            }
        } else if (i != (from ^ 1)) {
            // because it's un-directed graph
            // so reverse edge is not allowed
            

            // TODO: why don't need to judge in stack
            // ANS: in strongly connected component, we use in_stack to identify cross edge,
            // we should not use cross edge linked vertex to update low[u]
            // But in undirected graph, we don't have a concept of cross edge because every edge is
            // undirected, so we don't need to use in_stack
            low[u] = min(low[u], dfn[v]);
        }
    }

    if (dfn[u] == low[u]) {
        ebcc_id++;

        int v;
        do {
            v = stk.top();
            stk.pop();
            in_stk[v] = false;
            ebcc[v] = ebcc_id;
        } while (v != u);
    }
}

int main () {
    memset(h, -1, sizeof h);

    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int u, v; cin >> u >> v;
        add(u, v); add(v, u);
    }

    for (int u = 1; u <= N; u++) {
        if (!dfn[u]) {
            tarjan_ebcc(u, -1);
        }
    }

    for (int u = 1; u <= N; u++) {
        for (int i = h[u]; i; i = nx[i]) {
            int v = to[i];

            int ebcc_u = ebcc[u], ebcc_v = ebcc[v];
            if (ebcc_u != ebcc_v) {
                d[ebcc_u]++;
            }
        }
    }

    return 0;
}

V-BCC

割点模版题

www.luogu.com.cn/problem/P33…

#include<bits/stdc++.h>
using namespace std;

const int MAX_N = 10005, MAX_M = 50005;

int N, M;

int h[MAX_N], to[2 * MAX_M], nx[2 * MAX_M], e_id;

int dfn[MAX_N], low[MAX_N], ts;

// base on the requirement
// stack is optional if we only need to calculate
// the bridge-edge or cut-vertex
stack<int> stk;

// vdcc[u] denotes the vdcc that u belongs to
// d[id] denotes how many degree the vdcc id has
int vdcc[MAX_N], d[MAX_N], vdcc_id;

bool is_root[MAX_N], is_cut_vertex[MAX_N];

void add (int u, int v) {
    to[e_id] = v, nx[e_id] = h[u], h[u] = e_id++;
}

void tarjan_vdcc (int u, int from_e) {
    dfn[u] = low[u] = ++ts;
    stk.push(u);

    int child = 0;
    for (int i = h[u]; ~i; i = nx[i]) {
        int v = to[i];
        if (!dfn[v]) {
            child++;
            tarjan_vdcc(v, i);
            low[u] = min(low[u], low[v]);
            if (
                    dfn[u] <= low[v] &&
                    (!is_root[u] || child > 1)
               ) {
                // when dfn[u] == low[v]
                // 1. u is root, then if it has more than one subtree
                //    then u is cut-vertex
                // 2. u i not root then it's cut-vertex
                // when dfn[u] < low[v]
                // 1. equal to situation 1 above
                // 2. equal to situation 2 above
                if (!is_cut_vertex[u]) {
                    is_cut_vertex[u] = true;
                    // Do something else such as collect cut vertex here
                }
            }
        } else if (i != (from_e ^ 1)) {
            // because it's un-directed graph
            // so reverse edge is not allowed
            low[u] = min(low[u], dfn[v]);
        }
    }

    // TODO: calculate vbcc
}

int main () {
    memset(h, -1, sizeof h);

    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int u, v; cin >> u >> v;
        add(u, v); add(v, u);
    }

    for (int u = 1; u <= N; u++) {
        if (!dfn[u]) {
            is_root[u] = true;
            tarjan_vdcc(u, -1);
        }
    }

    return 0;
}

求 V-BCC