Skip to content

Commit 735d548

Browse files
authored
Merge pull request #339 from hitonanode/incremental-scc
Incremental SCC
2 parents b48cffb + b417edf commit 735d548

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

graph/incremental_scc.hpp

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <tuple>
5+
#include <utility>
6+
#include <vector>
7+
8+
#include "graph/strongly_connected_components.hpp"
9+
10+
// edges[i] = (s, t) means that the edge (s, t) is added at i-th tick.
11+
// Return the earliest tick when the edge (s, t) is included in a cycle.
12+
// If the edge (s, t) is never included in a cycle or s == t, return M.
13+
// Complexity: O(M log M), where M = edges.size()
14+
// Verified: https://codeforces.com/contest/1989/submission/268026664
15+
std::vector<int> incremental_scc(const std::vector<std::pair<int, int>> &edges) {
16+
int N = 1;
17+
for (auto [s, t] : edges) N = std::max({N, s + 1, t + 1});
18+
19+
const int M = edges.size();
20+
21+
std::vector<int> ret(M, M);
22+
23+
std::vector<int> compressed_idx(N, -1);
24+
25+
using Edges = std::vector<std::tuple<int, int, int>>;
26+
27+
auto rec = [&](auto &&self, const Edges &e, int tl, int tr) -> void {
28+
if (e.empty() or tl + 1 == tr) return;
29+
30+
int n = 0;
31+
for (const auto &[tick, s, t] : e) {
32+
if (compressed_idx.at(s) == -1) compressed_idx.at(s) = n++;
33+
if (compressed_idx.at(t) == -1) compressed_idx.at(t) = n++;
34+
}
35+
36+
const int tmid = (tl + tr) / 2;
37+
38+
DirectedGraphSCC scc(n);
39+
for (const auto &[tick, s, t] : e) {
40+
if (tick < tmid) scc.add_edge(compressed_idx.at(s), compressed_idx.at(t));
41+
}
42+
scc.FindStronglyConnectedComponents();
43+
44+
Edges left, right;
45+
46+
for (const auto &[tick, s, t] : e) {
47+
const int sc = compressed_idx.at(s), tc = compressed_idx.at(t);
48+
if (tick < tmid and scc.cmp.at(sc) == scc.cmp.at(tc)) {
49+
ret.at(tick) = tmid - 1;
50+
left.emplace_back(tick, sc, tc);
51+
} else {
52+
right.emplace_back(tick, scc.cmp.at(sc), scc.cmp.at(tc));
53+
}
54+
}
55+
56+
for (auto [_, s, t] : e) compressed_idx.at(s) = compressed_idx.at(t) = -1;
57+
58+
self(self, left, tl, tmid);
59+
self(self, right, tmid, tr);
60+
};
61+
62+
Edges init;
63+
init.reserve(M);
64+
for (int tick = 0; tick < M; ++tick) {
65+
if (auto [s, t] = edges.at(tick); s != t) init.emplace_back(tick, s, t);
66+
}
67+
68+
rec(rec, init, 0, M + 1);
69+
70+
return ret;
71+
}

graph/incremental_scc.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Incremental SCC (強連結成分)
3+
documentation_of: ./incremental_scc.hpp
4+
---
5+
6+
$m$ 個の有向辺からなる列が与えられ,先頭の要素から順にグラフに追加していく.各有向辺について,グラフに何番目の辺まで追加したときに初めてその辺を含む閉路ができるかを $O(m \log m)$ で計算する.
7+
8+
この処理は以下のような用途に使える.
9+
10+
- UnionFind などのデータ構造を併用することで,各時点での強連結成分を管理できる.
11+
- 各辺を含む閉路ができる時刻を重みとして最小全域木を求め,更に heavy-light decomposition やセグメント木と併用することで, 2 頂点が同一の強連結成分に初めて属する時刻をクエリ $O(n \log n)$ 等で計算できる.
12+
13+
## 使用方法
14+
15+
```cpp
16+
vector<pair<int, int>> edges; // 有向辺の列. edges[i] は時刻 i に追加される
17+
18+
auto ticks = incremental_scc(edges);
19+
20+
assert(ticks.size() == edges.size());
21+
// ticks[i] = (edges[i] を含む閉路ができる時刻 (0 <= ticks[i] < m)) または m (閉路ができない場合・自己ループの場合)
22+
```
23+
24+
## 問題例
25+
26+
- [Educational Codeforces Round 167 (Rated for Div. 2) F. Simultaneous Coloring](https://codeforces.com/contest/1989/problem/F)
27+
28+
## リンク
29+
30+
- [My own algorithm — offline incremental strongly connected components in O(m*log(m)) - Codeforces](https://codeforces.com/blog/entry/91608)

0 commit comments

Comments
 (0)