diff --git a/docs/strongly-connected-components.md b/docs/strongly-connected-components.md index 6dcc3782..abedb0a7 100644 --- a/docs/strongly-connected-components.md +++ b/docs/strongly-connected-components.md @@ -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 になっている。 @@ -29,7 +29,7 @@ explicit StronglyConnectedComponents(int n) void build() ``` -強連結成分分解する。`dag` には縮約後の頂点と辺からなる DAG が格納される。`comp` には各頂点が属する強連結成分の頂点番号が格納される。`group` には各強連結成分について、それに属する頂点が格納される。 +強連結成分分解する。`dag` には縮約後の頂点と辺からなる DAG が格納される。`comp` には各頂点が属する強連結成分の頂点番号がトポロジカル順で格納される。`group` には各強連結成分について、それに属する頂点が格納される。辺を追加した後に再度呼び出すこともできる。 ## 計算量 diff --git a/graph/connected-components/strongly-connected-components.hpp b/graph/connected-components/strongly-connected-components.hpp index 83b9bd9c..ae6a251a 100644 --- a/graph/connected-components/strongly-connected-components.hpp +++ b/graph/connected-components/strongly-connected-components.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -10,27 +9,27 @@ template struct StronglyConnectedComponents : Graph { - public: using Graph::Graph; using Graph::g; std::vector comp; Graph dag; - std::vector > group; + std::vector> group; void build() { - rg = Graph(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(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(ptr); for (std::size_t i = 0; i < g.size(); i++) { for (auto& e : g[i]) { @@ -39,7 +38,7 @@ struct StronglyConnectedComponents : Graph { 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); } @@ -48,18 +47,44 @@ struct StronglyConnectedComponents : Graph { int operator[](int k) const { return comp[k]; } private: - std::vector order, used; - Graph rg; + std::vector order, low, in_stack, vertex_stack; + std::vector> 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++; + } } }; diff --git a/test/unittest/strongly-connected-components.test.cpp b/test/unittest/strongly-connected-components.test.cpp new file mode 100644 index 00000000..cb048481 --- /dev/null +++ b/test/unittest/strongly-connected-components.test.cpp @@ -0,0 +1,87 @@ +// competitive-verifier: STANDALONE + +#include "../../graph/connected-components/strongly-connected-components.hpp" + +#include +#include +#include + +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(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> reachable(n, std::vector(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); + } +} diff --git a/test/verify/aoj-2450.test.cpp b/test/verify/aoj-2450.test.cpp index e029f678..1e3cc88f 100644 --- a/test/verify/aoj-2450.test.cpp +++ b/test/verify/aoj-2450.test.cpp @@ -14,7 +14,7 @@ using namespace std; -constexpr int64_t kInfinity = numeric_limits::max(); +constexpr int64_t kInfinity = (1LL << 62) - 1; int main() { int N, Q;