From b417edfac55c7fcc954762c13f3cb1176ac25a9e Mon Sep 17 00:00:00 2001 From: hitonanode <32937551+hitonanode@users.noreply.github.com> Date: Sat, 6 Jul 2024 10:40:57 +0900 Subject: [PATCH] add incremental scc --- graph/incremental_scc.hpp | 71 +++++++++++++++++++++++++++++++++++++++ graph/incremental_scc.md | 30 +++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 graph/incremental_scc.hpp create mode 100644 graph/incremental_scc.md diff --git a/graph/incremental_scc.hpp b/graph/incremental_scc.hpp new file mode 100644 index 00000000..59002a97 --- /dev/null +++ b/graph/incremental_scc.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +#include "graph/strongly_connected_components.hpp" + +// edges[i] = (s, t) means that the edge (s, t) is added at i-th tick. +// Return the earliest tick when the edge (s, t) is included in a cycle. +// If the edge (s, t) is never included in a cycle or s == t, return M. +// Complexity: O(M log M), where M = edges.size() +// Verified: https://codeforces.com/contest/1989/submission/268026664 +std::vector incremental_scc(const std::vector> &edges) { + int N = 1; + for (auto [s, t] : edges) N = std::max({N, s + 1, t + 1}); + + const int M = edges.size(); + + std::vector ret(M, M); + + std::vector compressed_idx(N, -1); + + using Edges = std::vector>; + + auto rec = [&](auto &&self, const Edges &e, int tl, int tr) -> void { + if (e.empty() or tl + 1 == tr) return; + + int n = 0; + for (const auto &[tick, s, t] : e) { + if (compressed_idx.at(s) == -1) compressed_idx.at(s) = n++; + if (compressed_idx.at(t) == -1) compressed_idx.at(t) = n++; + } + + const int tmid = (tl + tr) / 2; + + DirectedGraphSCC scc(n); + for (const auto &[tick, s, t] : e) { + if (tick < tmid) scc.add_edge(compressed_idx.at(s), compressed_idx.at(t)); + } + scc.FindStronglyConnectedComponents(); + + Edges left, right; + + for (const auto &[tick, s, t] : e) { + const int sc = compressed_idx.at(s), tc = compressed_idx.at(t); + if (tick < tmid and scc.cmp.at(sc) == scc.cmp.at(tc)) { + ret.at(tick) = tmid - 1; + left.emplace_back(tick, sc, tc); + } else { + right.emplace_back(tick, scc.cmp.at(sc), scc.cmp.at(tc)); + } + } + + for (auto [_, s, t] : e) compressed_idx.at(s) = compressed_idx.at(t) = -1; + + self(self, left, tl, tmid); + self(self, right, tmid, tr); + }; + + Edges init; + init.reserve(M); + for (int tick = 0; tick < M; ++tick) { + if (auto [s, t] = edges.at(tick); s != t) init.emplace_back(tick, s, t); + } + + rec(rec, init, 0, M + 1); + + return ret; +} diff --git a/graph/incremental_scc.md b/graph/incremental_scc.md new file mode 100644 index 00000000..3e5996e2 --- /dev/null +++ b/graph/incremental_scc.md @@ -0,0 +1,30 @@ +--- +title: Incremental SCC (強連結成分) +documentation_of: ./incremental_scc.hpp +--- + +$m$ 個の有向辺からなる列が与えられ,先頭の要素から順にグラフに追加していく.各有向辺について,グラフに何番目の辺まで追加したときに初めてその辺を含む閉路ができるかを $O(m \log m)$ で計算する. + +この処理は以下のような用途に使える. + +- UnionFind などのデータ構造を併用することで,各時点での強連結成分を管理できる. +- 各辺を含む閉路ができる時刻を重みとして最小全域木を求め,更に heavy-light decomposition やセグメント木と併用することで, 2 頂点が同一の強連結成分に初めて属する時刻をクエリ $O(n \log n)$ 等で計算できる. + +## 使用方法 + +```cpp +vector> edges; // 有向辺の列. edges[i] は時刻 i に追加される + +auto ticks = incremental_scc(edges); + +assert(ticks.size() == edges.size()); +// ticks[i] = (edges[i] を含む閉路ができる時刻 (0 <= ticks[i] < m)) または m (閉路ができない場合・自己ループの場合) +``` + +## 問題例 + +- [Educational Codeforces Round 167 (Rated for Div. 2) F. Simultaneous Coloring](https://codeforces.com/contest/1989/problem/F) + +## リンク + +- [My own algorithm — offline incremental strongly connected components in O(m*log(m)) - Codeforces](https://codeforces.com/blog/entry/91608)