Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/strongly-connected-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ documentation_of: //graph/connected-components/strongly-connected-components.hpp

グラフの任意の $2$ 頂点間に有向路が存在するとき、有向グラフが強連結であるとよぶ。強連結成分は、極大で強連結な部分グラフである。

適当な頂点から DFS をして、帰りがけ順に頂点を列挙することを、未訪問の頂点がある間繰り返す。次に辺をすべて逆向きにしたグラフについて、列挙した頂点の逆順に DFS する。$1$ 回の DFS で到達できた頂点が $1$ つの強連結成分となる。
各頂点の DFS における訪問順 `ord` と、DFS 木の部分木から到達できる訪問中の頂点の最小訪問順 `low` を求める。`ord` と `low` が等しい頂点を根とする部分が $1$ つの強連結成分となる。

強連結成分を縮約後の頂点とそれらを結ぶ辺からなるグラフは DAG になっている。

Expand All @@ -29,7 +29,7 @@ explicit StronglyConnectedComponents(int n)
void build()
```

強連結成分分解する。`dag` には縮約後の頂点と辺からなる DAG が格納される。`comp` には各頂点が属する強連結成分の頂点番号が格納される。`group` には各強連結成分について、それに属する頂点が格納される。
強連結成分分解する。`dag` には縮約後の頂点と辺からなる DAG が格納される。`comp` には各頂点が属する強連結成分の頂点番号がトポロジカル順で格納される。`group` には各強連結成分について、それに属する頂点が格納される。辺を追加した後に再度呼び出すこともできる。

## 計算量

Expand Down
77 changes: 51 additions & 26 deletions graph/connected-components/strongly-connected-components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,34 @@

#include <algorithm>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>

#include "../graph-template.hpp"

template <typename T = int>
struct StronglyConnectedComponents : Graph<T> {
public:
using Graph<T>::Graph;
using Graph<T>::g;
std::vector<int> comp;
Graph<T> dag;
std::vector<std::vector<int> > group;
std::vector<std::vector<int>> group;

void build() {
rg = Graph<T>(g.size());
comp.assign(g.size(), -1);
order.assign(g.size(), -1);
low.resize(g.size());
in_stack.assign(g.size(), false);
dfs_stack.clear();
dfs_stack.reserve(g.size());
vertex_stack.clear();
vertex_stack.reserve(g.size());
int now = 0, ptr = 0;
for (std::size_t i = 0; i < g.size(); i++) {
for (auto& e : g[i]) {
rg.add_directed_edge(e.to, e.from, e.cost);
}
if (order[i] == -1) dfs(static_cast<int>(i), now, ptr);
}
comp.assign(g.size(), -1);
used.assign(g.size(), 0);
for (std::size_t i = 0; i < g.size(); i++) dfs(i);
std::reverse(std::begin(order), std::end(order));
int ptr = 0;
for (int i : order)
if (comp[i] == -1) rdfs(i, ptr), ptr++;
for (int& component : comp) component = ptr - 1 - component;

dag = Graph<T>(ptr);
for (std::size_t i = 0; i < g.size(); i++) {
for (auto& e : g[i]) {
Expand All @@ -39,7 +38,7 @@ struct StronglyConnectedComponents : Graph<T> {
dag.add_directed_edge(x, y, e.cost);
}
}
group.resize(ptr);
group.assign(ptr, {});
for (std::size_t i = 0; i < g.size(); i++) {
group[comp[i]].emplace_back(i);
}
Expand All @@ -48,18 +47,44 @@ struct StronglyConnectedComponents : Graph<T> {
int operator[](int k) const { return comp[k]; }

private:
std::vector<int> order, used;
Graph<T> rg;
std::vector<int> order, low, in_stack, vertex_stack;
std::vector<std::pair<int, std::size_t>> dfs_stack;

void dfs(int idx) {
if (std::exchange(used[idx], true)) return;
for (auto& to : g[idx]) dfs(to);
order.push_back(idx);
}
void dfs(int root, int& now, int& component_count) {
dfs_stack.clear();
order[root] = low[root] = now++;
in_stack[root] = true;
vertex_stack.push_back(root);
dfs_stack.emplace_back(root, 0);
while (!dfs_stack.empty()) {
auto& [v, edge_index] = dfs_stack.back();
if (edge_index < g[v].size()) {
int to = g[v][edge_index++].to;
if (order[to] == -1) {
order[to] = low[to] = now++;
in_stack[to] = true;
vertex_stack.push_back(to);
dfs_stack.emplace_back(to, 0);
} else if (in_stack[to]) {
low[v] = std::min(low[v], order[to]);
}
continue;
}

void rdfs(int idx, int cnt) {
if (comp[idx] != -1) return;
comp[idx] = cnt;
for (auto& to : rg.g[idx]) rdfs(to, cnt);
dfs_stack.pop_back();
if (!dfs_stack.empty()) {
int parent = dfs_stack.back().first;
low[parent] = std::min(low[parent], low[v]);
}
if (low[v] != order[v]) continue;
while (true) {
int u = vertex_stack.back();
vertex_stack.pop_back();
in_stack[u] = false;
comp[u] = component_count;
if (u == v) break;
}
component_count++;
}
}
};
87 changes: 87 additions & 0 deletions test/unittest/strongly-connected-components.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// competitive-verifier: STANDALONE

#include "../../graph/connected-components/strongly-connected-components.hpp"

#include <cassert>
#include <random>
#include <vector>

int main() {
{
StronglyConnectedComponents<> graph(8);
graph.add_directed_edge(0, 1);
graph.add_directed_edge(1, 2);
graph.add_directed_edge(2, 0);
graph.add_directed_edge(2, 3);
graph.add_directed_edge(3, 4);
graph.add_directed_edge(4, 3);
graph.add_directed_edge(4, 5);
graph.add_directed_edge(6, 7);
graph.build();

assert(graph[0] == graph[1] && graph[1] == graph[2]);
assert(graph[3] == graph[4]);
assert(graph[2] != graph[3]);
assert(graph[3] != graph[5]);
assert(graph[6] != graph[7]);
assert(graph[2] < graph[3] && graph[3] < graph[5]);
assert(graph[6] < graph[7]);

graph.add_directed_edge(5, 0);
graph.build();
for (int v = 1; v <= 5; v++) assert(graph[v] == graph[0]);
int grouped_vertices = 0;
for (const auto& component : graph.group) {
grouped_vertices += static_cast<int>(component.size());
}
assert(grouped_vertices == 8);
}

std::mt19937 rng(123456789);
for (int n = 1; n <= 30; n++) {
for (int iteration = 0; iteration < 30; iteration++) {
StronglyConnectedComponents<> graph(n);
std::vector<std::vector<bool>> reachable(n, std::vector<bool>(n, false));
for (int v = 0; v < n; v++) reachable[v][v] = true;
for (int from = 0; from < n; from++) {
for (int to = 0; to < n; to++) {
if (rng() % 5 == 0) {
graph.add_directed_edge(from, to);
reachable[from][to] = true;
}
}
}
for (int k = 0; k < n; k++) {
for (int from = 0; from < n; from++) {
for (int to = 0; to < n; to++) {
reachable[from][to] =
reachable[from][to] || (reachable[from][k] && reachable[k][to]);
}
}
}

graph.build();
for (int from = 0; from < n; from++) {
for (int to = 0; to < n; to++) {
bool same_component = reachable[from][to] && reachable[to][from];
assert((graph[from] == graph[to]) == same_component);
if (reachable[from][to] && graph[from] != graph[to]) {
assert(graph[from] < graph[to]);
}
}
}
}
}

{
constexpr int kVertices = 300000;
StronglyConnectedComponents<> graph(kVertices);
for (int v = 1; v < kVertices; v++) {
graph.add_directed_edge(v - 1, v);
}
graph.build();
assert(graph.group.size() == kVertices);
assert(graph[0] == 0);
assert(graph[kVertices - 1] == kVertices - 1);
}
}
2 changes: 1 addition & 1 deletion test/verify/aoj-2450.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

using namespace std;

constexpr int64_t kInfinity = numeric_limits<int64_t>::max();
constexpr int64_t kInfinity = (1LL << 62) - 1;

int main() {
int N, Q;
Expand Down
Loading