Skip to content

Commit

Permalink
Merge pull request #339 from hitonanode/incremental-scc
Browse files Browse the repository at this point in the history
Incremental SCC
  • Loading branch information
hitonanode authored Jul 6, 2024
2 parents b48cffb + b417edf commit 735d548
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
71 changes: 71 additions & 0 deletions graph/incremental_scc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#pragma once

#include <algorithm>
#include <tuple>
#include <utility>
#include <vector>

#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<int> incremental_scc(const std::vector<std::pair<int, int>> &edges) {
int N = 1;
for (auto [s, t] : edges) N = std::max({N, s + 1, t + 1});

const int M = edges.size();

std::vector<int> ret(M, M);

std::vector<int> compressed_idx(N, -1);

using Edges = std::vector<std::tuple<int, int, int>>;

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;
}
30 changes: 30 additions & 0 deletions graph/incremental_scc.md
Original file line number Diff line number Diff line change
@@ -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<pair<int, int>> 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)

0 comments on commit 735d548

Please sign in to comment.