Skip to content

Commit b417edf

Browse files
committed
add incremental scc
1 parent b48cffb commit b417edf

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

graph/incremental_scc.hpp

Lines changed: 71 additions & 0 deletions
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

Lines changed: 30 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy