离线 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
割点模版题
#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;
}