Skip to Content

BCCs y 2CCs

Recursos
FuenteRecursoNotas
CFDFS Tree + Bridges
CP24.2.8 - Articulation Points & Bridges

Componentes 2-arista-conexas

HechoFuenteNombreDificultadTagsSolución
YSTwo-Edge-Connected ComponentsFácil2CCen el módulo

Implementación

#include <bits/stdc++.h> using namespace std; const int MAX_N = 3e5; int timer; // Tiempo de entrada en el nodo int scc; // Número de componentes fuertemente conexas int id[MAX_N]; int low[MAX_N]; // ID más bajo en el subárbol del nodo en el árbol DFS vector<int> neighbors[MAX_N]; vector<int> two_edge_components[MAX_N]; stack<int> st; // Lleva el registro del camino en nuestro DFS void dfs(int node, int parent = -1) { id[node] = low[node] = ++timer; st.push(node); bool multiple_edges = false; for (int child : neighbors[node]) { if (child == parent && !multiple_edges) { multiple_edges = true; continue; } if (!id[child]) { dfs(child, node); low[node] = min(low[node], low[child]); } else { low[node] = min(low[node], id[child]); } } if (low[node] == id[node]) { while (st.top() != node) { two_edge_components[scc].push_back(st.top()); st.pop(); } two_edge_components[scc++].push_back(st.top()); st.pop(); } } int main() { int n, m; cin >> n >> m; for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; neighbors[x].push_back(y); neighbors[y].push_back(x); } for (int node = 0; node < n; node++) { if (id[node] == 0) { dfs(node); } } cout << scc << '\n'; for (int i = 0; i < scc; i++) { cout << two_edge_components[i].size() << ' '; for (int node : two_edge_components[i]) { cout << node << ' '; } cout << '\n'; } }

Con DSU

HechoFuenteNombreDificultadTagsSolución
PlatinumDisruptionNormalMergingSolución

El análisis del problema de arriba menciona una solución O(mα(n))\mathcal{O}(m\alpha(n)). Aunque esto no es un problema de componentes 2-conexas, de hecho podemos usar DSU para generar componentes 2-conexas.

Las operaciones de DSU toman O(logn)\mathcal{O}(\log n) en lugar de O(α(n))\mathcal{O}(\alpha(n)) porque el DSU no usa unión por tamaño, pero es fácil cambiar esto.

struct TwoEdgeCC { struct { vi e; void init(int n) { e = vi(n, -1); } int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); } bool unite(int x, int y) { // set par[y] = x x = get(x), y = get(y); if (x == y) return 0; e[x] += e[y]; e[y] = x; return 1; } } DSU; int N; vector<vi> adj; vi depth, par; vpi extra; void init(int _N) { N = _N; DSU.init(N); adj.rsz(N), depth.rsz(N), par = vi(N, -1); } void dfs(int x) { trav(t, adj[x]) if (t != par[x]) par[t] = x, depth[t] = depth[x] + 1, dfs(t); } void ae(int a, int b) { if (DSU.unite(a, b)) adj[a].pb(b), adj[b].pb(a); // edge of forest else extra.pb({a, b}); // extra edge } void ad(int a, int b) { while (1) { a = DSU.get(a), b = DSU.get(b); if (a == b) return; if (depth[a] < depth[b]) swap(a, b); assert(par[a] != -1 && DSU.unite(par[a], a)); } } void gen() { F0R(i, N) if (par[i] == -1) dfs(i); // independently for each connected component DSU.init(N); trav(t, extra) ad(t.f, t.s); // add non-spanning edges };

Problemas

HechoFuenteNombreDificultadTagsSolución
CEOI2017 - One-Way StreetsFácilBCC
  • SRM 787 1000

Componentes biconexas 

HechoFuenteNombreDificultadTagsSolución
CSESForbidden CitiesNormalBCCen el módulo

Explicación

Una observación importante es que si no se puede ir del nodo aa al nodo bb sin pasar por el nodo cc, el nodo cc es un nodo crítico (punto de articulación). El nodo cc puede separar aa y bb en 2 componentes distintas si se elimina. Esto nos hace pensar en componentes biconexas.

Ahora quedan dos casos. Si el nodo cc no es crítico, un camino de aa a bb puede evitar el nodo. En caso contrario, si el nodo cc es crítico, hay que comprobar si está en el camino de aa a bb. Aquí hay un poco de truco: en un grafo simple no es tan fácil de comprobar; en cambio, comprobarlo en un árbol puede ser mucho más fácil. Para ello, transformamos el grafo en un árbol block-cut .

En un árbol block-cut, cada articulación y cada componente biconexa representa un nodo. Ahora que convertimos nuestro grafo en un árbol, ¿cómo comprobamos si el camino de aa a bb pasa por cc? Para esto usamos LCA. Se puede leer más aquí .

Implementación

#include <bits/stdc++.h> using namespace std; /** @return the block-cut tree of a graph */ vector<vector<int>> biconnected_components(vector<vector<int>> &g, vector<bool> &is_cutpoint, vector<int> &id) { int n = (int)g.size(); vector<vector<int>> comps; vector<int> stk; vector<int> num(n); vector<int> low(n); is_cutpoint.resize(n); // Halla las componentes biconexas function<void(int, int, int &)> dfs = [&](int node, int parent, int &timer) { num[node] = low[node] = ++timer; stk.push_back(node); for (int son : g[node]) { if (son == parent) { continue; } if (num[son]) { low[node] = min(low[node], num[son]); } else { dfs(son, node, timer); low[node] = min(low[node], low[son]); if (low[son] >= num[node]) { is_cutpoint[node] = (num[node] > 1 || num[son] > 2); comps.push_back({node}); while (comps.back().back() != son) { comps.back().push_back(stk.back()); stk.pop_back(); } } } } }; int timer = 0; dfs(0, -1, timer); id.resize(n); // Construir el árbol block-cut function<vector<vector<int>>()> build_tree = [&]() { vector<vector<int>> t(1); int node_id = 0; for (int node = 0; node < n; node++) { if (is_cutpoint[node]) { id[node] = node_id++; t.push_back({}); } } for (auto &comp : comps) { int node = node_id++; t.push_back({}); for (int u : comp) if (!is_cutpoint[u]) { id[u] = node; } else { t[node].push_back(id[u]); t[id[u]].push_back(node); } } return t; }; return build_tree(); } int main() { int n, m, q; cin >> n >> m >> q; const int LOGMAX = (int)ceil(log2(n)); vector<vector<int>> g; g.resize(n); for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; x--, y--; g[x].push_back(y); g[y].push_back(x); } vector<int> id, depth(3 * n, 0); vector<bool> is_cutpoint; vector<vector<int>> up(3 * n, vector<int>(LOGMAX)); vector<vector<int>> blockcut_tree = biconnected_components(g, is_cutpoint, id); // Calcular el LCA function<void(int, int)> dfs = [&](int node, int parent) { if (parent != -1) { depth[node] = depth[parent] + 1; up[node][0] = parent; for (int j = 1; j < LOGMAX; j++) { up[node][j] = up[up[node][j - 1]][j - 1]; } } for (auto son : blockcut_tree[node]) { if (son == parent) continue; dfs(son, node); } }; auto find_lca = [&](int x, int y) { if (depth[x] < depth[y]) { swap(x, y); } int diff = depth[x] - depth[y]; for (int i = LOGMAX; i >= 0; i--) { if ((1 << i) & diff) { x = up[x][i]; } } if (x == y) { return x; } for (int i = LOGMAX; i >= 0; i--) { if (up[x][i] != up[y][i]) { x = up[x][i]; y = up[y][i]; } } return up[x][0]; }; // Comprobar si el nodo z está en el camino (x, y) auto on_path = [&](int x, int y, int z) { int lca = find_lca(x, y); int lca1 = find_lca(x, z); int lca2 = find_lca(y, z); if (lca == z || (lca1 == lca && lca2 == z) || (lca2 == lca && lca1 == z)) { return true; } return false; }; dfs(0, -1); for (int i = 0; i < q; i++) { int a, b, c; cin >> a >> b >> c; a--, b--, c--; // El camino no existe en dos casos: // 1) a == c o b == c // 2) c es un punto de corte y está en el camino entre a y b en el // árbol block-cut if (a == c || b == c || (is_cutpoint[c] && on_path(id[a], id[b], id[c]))) { std::cout << "NO\n"; } else { cout << "YES" << '\n'; } } }

Puntos de articulación

HechoFuenteNombreDificultadTagsSolución
SPOJSUBMERGE - Submerging IslandsNormalBCC, Articulation Pointsen el módulo
Recursos
FuenteRecursoNotas
CP2Articulation Points (aka Cut Vertices)

quizá no del todo correcto

Implementación

#include <bits/stdc++.h> using namespace std; const int NMAX = 2e4; int timer; vector<int> low, id; vector<bool> visited, ap; vector<vector<int>> g(NMAX); void dfs(int node, int parent) { visited[node] = true; id[node] = low[node] = timer++; int children = 0; for (int son : g[node]) { if (son == parent) { continue; } if (!visited[son]) { dfs(son, node); low[node] = min(low[node], low[son]); children++; // Comprobar si el nodo actual es un punto de articulación if (low[son] >= id[node] && parent != -1) { ap[node] = true; } } else { low[node] = min(low[node], id[son]); } } if (parent == -1 && children > 1) { ap[node] = true; } } int main() { int n, m; while (cin >> n >> m && n && m) { id.assign(n, 0); ap.assign(n, 0); low.assign(n, 0); visited.assign(n, 0); for (int i = 0; i < n; i++) { g[i].clear(); } timer = 0; for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; g[--x].push_back(--y); g[y].push_back(x); } dfs(0, -1); int articualation_points = 0; for (int i = 0; i < n; i++) { if (ap[i]) { articualation_points++; } } cout << articualation_points << '\n'; } }

Problemas

HechoFuenteNombreDificultadTagsSolución
CSESStrongly Connected EdgesFácilBCCSolución
POI2008 - BlockadeNormalBCCSolución
APIO2018 - DuathlonNormalBCCSolución
POI2016 - Amusing JourneysNormalBCC
TLEInvestmentDifícilBCC
CEOI2015 - PipesDifícilBCCSolución
PlatinumPush a BoxMuy difícilBCC