競技プログラミング用 C++ ライブラリ
graph/low_link.hpp · GitHub で見る
共通ページ
#include "lib/graph/low_link.hpp"
LowLink graph(3);
int id = graph.add_edge(0, 1);
graph.add_edge(1, 2);
graph.build();
bool bridge = graph.is_bridge(id); // true
auto vertices = graph.articulation; // {1}
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
LowLink(n) |
頂点数を指定 | \(O(1)\) |
add_edge(u,v) |
無向辺を追加し、辺 ID を返す | 償却 \(O(1)\) |
build() |
関節点・橋を計算。辺の追加後は再実行 | \(O(n+m)\) |
is_bridge(id) |
辺 ID が橋か | \(O(1)\) |
articulation / bridges
|
関節点の一覧/橋の辺 ID の一覧 | \(O(1)\) |
ord[v] / low[v]
|
DFS の訪問順/後退辺を使って到達できる最小の訪問順 | \(O(1)\) |
n / edges[id]
|
全頂点数/辺の両端 | \(O(1)\) |
無向グラフを扱います。頂点番号は 0 始まり、add_edge(u, v) は辺 ID を返します。
build() 後に articulation から関節点、bridges から橋の辺 ID、edges[id] から辺の両端を取得できます。is_bridge(id) で橋か判定できます。辺を追加した後は build() を再実行します。
\(n\) 頂点・\(m\) 辺として、build() は時間・空間ともに \(O(n+m)\)、add_edge は償却 \(O(1)\)、is_bridge は \(O(1)\) です。
#ifndef LOW_LINK_HPP
#define LOW_LINK_HPP
#include "../components/undirected_csr.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
struct LowLink {
using Arc = UndirectedCSR::Arc;
struct Frame {
int v, parent, next;
};
int n;
std::vector<std::pair<int, int>> edges;
std::vector<int> ord, low, articulation, bridges;
explicit LowLink(int n) : n(n) { assert(n >= 0); }
int add_edge(int u, int v) {
assert(0 <= u && u < n && 0 <= v && v < n);
assert(edges.size() < std::size_t(std::numeric_limits<int>::max() / 2));
int id = static_cast<int>(edges.size());
edges.emplace_back(u, v);
return id;
}
void build() {
assert(n >= 0 && edges.size() <= std::size_t(std::numeric_limits<int>::max() / 2));
UndirectedCSR graph(n, edges);
const auto &start = graph.start;
const auto &adj = graph.adj;
ord.assign(n, -1);
low.assign(n, -1);
articulation.clear();
bridges.clear();
std::vector<std::uint8_t> cut(n);
std::vector<Frame> stack;
stack.reserve(n);
int timer = 0;
for (int root = 0; root < n; ++root) {
if (ord[root] != -1) continue;
int children = 0;
ord[root] = low[root] = timer++;
stack.push_back({root, -1, start[root]});
while (!stack.empty()) {
auto &f = stack.back();
int v = f.v;
if (f.next == start[v + 1]) {
int id = f.parent;
stack.pop_back();
if (stack.empty()) cut[v] = children > 1;
else {
int p = stack.back().v;
low[p] = std::min(low[p], low[v]);
if (ord[p] < low[v]) bridges.push_back(id);
if (p != root && ord[p] <= low[v]) cut[p] = 1;
}
continue;
}
auto [to, id] = adj[f.next++];
if (id == f.parent) continue;
if (ord[to] != -1) {
low[v] = std::min(low[v], ord[to]);
continue;
}
children += v == root;
ord[to] = low[to] = timer++;
stack.push_back({to, id, start[to]});
}
}
for (int v = 0; v < n; ++v)
if (cut[v]) articulation.push_back(v);
}
bool is_bridge(int id) const {
assert(0 <= id && id < static_cast<int>(edges.size()) && static_cast<int>(ord.size()) == n);
auto [u, v] = edges[id];
if (ord[u] > ord[v]) std::swap(u, v);
return ord[u] < low[v];
}
};
#endif
#line 1 "graph/low_link.hpp"
#line 1 "components/undirected_csr.hpp"
#include <cassert>
#include <cstddef>
#include <limits>
#include <memory>
#include <span>
#include <type_traits>
#include <utility>
#include <vector>
namespace undirected_csr_detail {
template <class Arc>
void build(int n, std::span<const std::pair<int, int>> edges, std::vector<int>& start, std::unique_ptr<Arc[]>& adj) {
assert(0 <= n && n < std::numeric_limits<int>::max());
assert(edges.size() <= std::size_t(std::numeric_limits<int>::max() / 2));
start.assign(std::size_t(n) + 1, 0);
for (auto [u, v] : edges) {
assert(0 <= u && u < n && 0 <= v && v < n);
++start[u + 1], ++start[v + 1];
}
for (int v = 0; v < n; ++v) start[v + 1] += start[v];
adj.reset(new Arc[2 * edges.size()]);
std::vector<int> cursor(start.begin(), start.end() - 1);
for (int i = 0; i < static_cast<int>(edges.size()); ++i) {
auto [u, v] = edges[i];
if constexpr (std::is_same_v<Arc, int>) adj[cursor[u]++] = v, adj[cursor[v]++] = u;
else adj[cursor[u]++] = {v, i}, adj[cursor[v]++] = {u, i};
}
}
}
struct UndirectedCSR {
struct Arc { int to, id; };
int n;
std::vector<int> start;
std::unique_ptr<Arc[]> adj;
UndirectedCSR(int n, std::span<const std::pair<int, int>> edges) : n(n) {
undirected_csr_detail::build(n, edges, start, adj);
}
std::span<const Arc> operator[](int v) const {
assert(0 <= v && v < n);
return {adj.get() + start[v], std::size_t(start[v + 1] - start[v])};
}
};
struct UndirectedVertexCSR {
int n;
std::vector<int> start;
std::unique_ptr<int[]> adj;
UndirectedVertexCSR(int n, std::span<const std::pair<int, int>> edges) : n(n) {
undirected_csr_detail::build(n, edges, start, adj);
}
std::span<const int> operator[](int v) const {
assert(0 <= v && v < n);
return {adj.get() + start[v], std::size_t(start[v + 1] - start[v])};
}
};
#line 5 "graph/low_link.hpp"
#include <algorithm>
#line 8 "graph/low_link.hpp"
#include <cstdint>
#line 13 "graph/low_link.hpp"
struct LowLink {
using Arc = UndirectedCSR::Arc;
struct Frame {
int v, parent, next;
};
int n;
std::vector<std::pair<int, int>> edges;
std::vector<int> ord, low, articulation, bridges;
explicit LowLink(int n) : n(n) { assert(n >= 0); }
int add_edge(int u, int v) {
assert(0 <= u && u < n && 0 <= v && v < n);
assert(edges.size() < std::size_t(std::numeric_limits<int>::max() / 2));
int id = static_cast<int>(edges.size());
edges.emplace_back(u, v);
return id;
}
void build() {
assert(n >= 0 && edges.size() <= std::size_t(std::numeric_limits<int>::max() / 2));
UndirectedCSR graph(n, edges);
const auto &start = graph.start;
const auto &adj = graph.adj;
ord.assign(n, -1);
low.assign(n, -1);
articulation.clear();
bridges.clear();
std::vector<std::uint8_t> cut(n);
std::vector<Frame> stack;
stack.reserve(n);
int timer = 0;
for (int root = 0; root < n; ++root) {
if (ord[root] != -1) continue;
int children = 0;
ord[root] = low[root] = timer++;
stack.push_back({root, -1, start[root]});
while (!stack.empty()) {
auto &f = stack.back();
int v = f.v;
if (f.next == start[v + 1]) {
int id = f.parent;
stack.pop_back();
if (stack.empty()) cut[v] = children > 1;
else {
int p = stack.back().v;
low[p] = std::min(low[p], low[v]);
if (ord[p] < low[v]) bridges.push_back(id);
if (p != root && ord[p] <= low[v]) cut[p] = 1;
}
continue;
}
auto [to, id] = adj[f.next++];
if (id == f.parent) continue;
if (ord[to] != -1) {
low[v] = std::min(low[v], ord[to]);
continue;
}
children += v == root;
ord[to] = low[to] = timer++;
stack.push_back({to, id, start[to]});
}
}
for (int v = 0; v < n; ++v)
if (cut[v]) articulation.push_back(v);
}
bool is_bridge(int id) const {
assert(0 <= id && id < static_cast<int>(edges.size()) && static_cast<int>(ord.size()) == n);
auto [u, v] = edges[id];
if (ord[u] > ord[v]) std::swap(u, v);
return ord[u] < low[v];
}
};