競技プログラミング用 C++ ライブラリ
graph/scc.hpp · GitHub で見る
共通ページ
#include "lib/graph/scc.hpp"
SCCGraph graph(4);
graph.add_edge(0, 1);
graph.add_edge(1, 0);
graph.add_edge(1, 2);
auto groups = graph.scc();
auto [count, id] = graph.scc_ids();
auto components = graph.decomposition();
for (int c = 0; c < components.size(); ++c) {
for (int v : components[c]) {
// 成分 c の頂点
}
}
SCCGraph(n) で 0 始まりの n 頂点を作り、add_edge(from,to) で有向辺を追加します。自己ループ・多重辺・空グラフにも対応します。
scc() は成分の配列を返し、各成分内の頂点は昇順です。成分の順は縮約 DAG のトポロジカル順で、異なる成分を結ぶ辺は必ず前から後へ向かいます。独立した成分間の順序は保証しません。
scc_ids() は成分数と長さ n の成分 ID 配列を返します。同じ成分の頂点は同じ ID を持ち、ID は scc() の成分番号と一致します。結果を保持しないため、呼ぶたびに再計算します。辺追加後も再利用でき、コピーは独立します。
decomposition() は次の Components を返します。成分の順序・各成分の頂点順は scc() と同じです。
| メンバー | 内容 |
|---|---|
size() |
成分数 |
id[v] |
頂点 v の成分 ID |
operator[](c) |
成分 c の頂点列を span<const int> で取得 |
vertices |
全成分の頂点列を連結した配列 |
offset |
成分 c が vertices の半開区間 [offset[c],offset[c+1]) に対応する境界配列 |
結果はグラフを参照せず、コピーは独立します。頂点列の span は、その結果の破棄・代入や配列の変更まで有効です。各メンバーの参照・頂点列の取得は \(O(1)\) です。
SCCGraph::size() は頂点数、edge_count() は辺数、reserve(m) は辺の格納容量を予約します。頂点数は非負の int、辺数は INT_MAX 以下、辺の端点は [0,n) が必要です。
頂点数を \(n\)、辺数を \(m\) として、構築は \(O(1)\)、辺追加は償却 \(O(1)\)、分解は \(O(n+m)\) 時間・\(O(n+m)\) 追加空間、グラフの保持は \(O(n+m)\) 空間です。
#ifndef SCC_HPP
#define SCC_HPP
#include "../components/scc_core.hpp"
class SCCGraph {
int n_;
std::vector<std::pair<int, int>> edges_;
public:
struct Components {
std::vector<int> id, vertices, offset;
Components() = default;
Components(const Components &) = default;
Components(Components &&other) noexcept { swap(other); }
Components &operator=(Components other) noexcept { swap(other); return *this; }
void swap(Components &other) noexcept { id.swap(other.id), vertices.swap(other.vertices), offset.swap(other.offset); }
int size() const { return offset.empty() ? 0 : static_cast<int>(offset.size() - 1); }
std::span<const int> operator[](int component) const {
assert(0 <= component && component < size());
return {vertices.data() + offset[component], std::size_t(offset[component + 1] - offset[component])};
}
};
explicit SCCGraph(int n = 0) : n_(n) { assert(n >= 0); }
int size() const { return n_; }
std::size_t edge_count() const { return edges_.size(); }
void reserve(std::size_t m) {
assert(m <= std::size_t(std::numeric_limits<int>::max()));
edges_.reserve(m);
}
void add_edge(int from, int to) {
assert(0 <= from && from < n_ && 0 <= to && to < n_);
assert(edges_.size() < std::size_t(std::numeric_limits<int>::max()));
edges_.emplace_back(from, to);
}
std::pair<int, std::vector<int>> scc_ids() const { return scc_detail::ids(n_, edges_); }
Components decomposition() const {
auto [count, id] = scc_ids();
Components result;
result.id = std::move(id);
result.offset.resize(std::size_t(count) + 1);
for (int x : result.id) ++result.offset[x + 1];
for (int i = 0; i < count; ++i) result.offset[i + 1] += result.offset[i];
result.vertices.resize(n_);
auto cursor = result.offset;
for (int v = 0; v < n_; ++v) result.vertices[cursor[result.id[v]]++] = v;
return result;
}
std::vector<std::vector<int>> scc() const {
auto [count, id] = scc_ids();
std::vector<int> sizes(count);
for (int x : id) ++sizes[x];
std::vector<std::vector<int>> groups(count);
for (int i = 0; i < count; ++i) groups[i].reserve(sizes[i]);
for (int v = 0; v < n_; ++v) groups[id[v]].push_back(v);
return groups;
}
};
#endif
#line 1 "graph/scc.hpp"
#line 1 "components/scc_core.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <limits>
#include <span>
#include <utility>
#include <vector>
namespace scc_detail {
template<bool RejectPairs = false>
inline std::pair<int, std::vector<int>> ids(int n, std::span<const std::pair<int, int>> edges) {
assert((!RejectPairs || n % 2 == 0) && n >= 0 && edges.size() <= std::size_t(std::numeric_limits<int>::max()));
std::vector<int> answer(n, -1);
unsigned monotone = 3;
for (auto [from, to] : edges) {
assert(0 <= from && from < n && 0 <= to && to < n);
monotone &= (from <= to ? 1U : 0U) | (from >= to ? 2U : 0U);
if (!monotone) break;
}
if (monotone) {
for (int v = 0; v < n; ++v) answer[v] = monotone & 1 ? v : n - 1 - v;
return {n, std::move(answer)};
}
std::vector<int> offset(std::size_t(n) + 1), degree(n);
for (auto [from, to] : edges) {
assert(0 <= from && from < n && 0 <= to && to < n);
++offset[from + 1];
++degree[to];
}
bool functional = true;
for (int v = 0; v < n; ++v) {
functional &= offset[v + 1] <= 1;
offset[v + 1] += offset[v];
}
std::vector<int> adjacency(edges.size()), work(offset.begin(), offset.end() - 1);
for (auto [from, to] : edges) adjacency[work[from]++] = to;
work.clear();
for (int v = 0; v < n; ++v) if (!degree[v]) work.push_back(v);
for (std::size_t head = 0; head < work.size(); ++head) {
int v = work[head];
answer[v] = static_cast<int>(head);
for (int j = offset[v]; j < offset[v + 1]; ++j)
if (--degree[adjacency[j]] == 0) work.push_back(adjacency[j]);
}
int peeled = static_cast<int>(work.size());
if (peeled == n) return {n, std::move(answer)};
if (functional) {
int count = peeled;
for (int v = 0; v < n; ++v) if (degree[v]) {
int u = v;
do {
answer[u] = count;
if constexpr (RejectPairs) if (answer[u ^ 1] == count) return {-1, {}};
degree[u] = 0;
u = adjacency[offset[u]];
} while (u != v);
++count;
}
return {count, std::move(answer)};
}
for (int v : work) answer[v] = -answer[v] - 1;
for (int &x : degree) x = x ? -1 : n;
work.clear();
struct Frame { int vertex, next, low; };
std::vector<Frame> stack;
stack.reserve(n - peeled);
int clock = 0, count = 0;
auto enter = [&](int v) {
degree[v] = clock++;
work.push_back(v);
stack.push_back({v, offset[v], degree[v]});
};
for (int root = 0; root < n; ++root) if (degree[root] == -1) {
enter(root);
while (!stack.empty()) {
auto &frame = stack.back();
int v = frame.vertex;
int next = frame.next, low = frame.low, end = offset[v + 1];
for (; next < end; ++next) {
int to = adjacency[next];
if (degree[to] == -1) break;
low = std::min(low, degree[to]);
}
frame.low = low;
if (next < end) {
frame.next = next + 1;
enter(adjacency[next]);
} else {
stack.pop_back();
if (low == degree[v]) {
int u;
do {
u = work.back();
work.pop_back();
degree[u] = n;
answer[u] = count;
if constexpr (RejectPairs) if (answer[u ^ 1] == count) return {-1, {}};
} while (u != v);
++count;
}
if (!stack.empty()) stack.back().low = std::min(stack.back().low, low);
}
}
}
for (int &x : answer) x = x < 0 ? -x - 1 : peeled + count - 1 - x;
return {peeled + count, std::move(answer)};
}
}
#line 5 "graph/scc.hpp"
class SCCGraph {
int n_;
std::vector<std::pair<int, int>> edges_;
public:
struct Components {
std::vector<int> id, vertices, offset;
Components() = default;
Components(const Components &) = default;
Components(Components &&other) noexcept { swap(other); }
Components &operator=(Components other) noexcept { swap(other); return *this; }
void swap(Components &other) noexcept { id.swap(other.id), vertices.swap(other.vertices), offset.swap(other.offset); }
int size() const { return offset.empty() ? 0 : static_cast<int>(offset.size() - 1); }
std::span<const int> operator[](int component) const {
assert(0 <= component && component < size());
return {vertices.data() + offset[component], std::size_t(offset[component + 1] - offset[component])};
}
};
explicit SCCGraph(int n = 0) : n_(n) { assert(n >= 0); }
int size() const { return n_; }
std::size_t edge_count() const { return edges_.size(); }
void reserve(std::size_t m) {
assert(m <= std::size_t(std::numeric_limits<int>::max()));
edges_.reserve(m);
}
void add_edge(int from, int to) {
assert(0 <= from && from < n_ && 0 <= to && to < n_);
assert(edges_.size() < std::size_t(std::numeric_limits<int>::max()));
edges_.emplace_back(from, to);
}
std::pair<int, std::vector<int>> scc_ids() const { return scc_detail::ids(n_, edges_); }
Components decomposition() const {
auto [count, id] = scc_ids();
Components result;
result.id = std::move(id);
result.offset.resize(std::size_t(count) + 1);
for (int x : result.id) ++result.offset[x + 1];
for (int i = 0; i < count; ++i) result.offset[i + 1] += result.offset[i];
result.vertices.resize(n_);
auto cursor = result.offset;
for (int v = 0; v < n_; ++v) result.vertices[cursor[result.id[v]]++] = v;
return result;
}
std::vector<std::vector<int>> scc() const {
auto [count, id] = scc_ids();
std::vector<int> sizes(count);
for (int x : id) ++sizes[x];
std::vector<std::vector<int>> groups(count);
for (int i = 0; i < count; ++i) groups[i].reserve(sizes[i]);
for (int v = 0; v < n_; ++v) groups[id[v]].push_back(v);
return groups;
}
};