From b459a160dbf0c1d027fb80a0ecbc77d579e725d0 Mon Sep 17 00:00:00 2001 From: Naoto Mizuno Date: Sat, 19 Sep 2020 20:34:00 +0900 Subject: [PATCH] Port SCC --- README.md | 2 +- README_ja.md | 2 +- atcoder/_scc.py | 101 ++++++++++++++++++++++++++++++++++++++++ atcoder/scc.py | 17 +++++++ example/scc_practice.py | 27 +++++++++++ 5 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 atcoder/_scc.py create mode 100644 atcoder/scc.py create mode 100644 example/scc_practice.py diff --git a/README.md b/README.md index 2abe4d9..b866021 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ ac-library-python is a Python port of [AtCoder Library (ACL)](https://atcoder.jp #### Graph + [Disjoint Set Union (DSU)](https://github.com/atcoder/ac-library/blob/master/document_en/dsu.md) ++ scc ### Work in progress @@ -41,7 +42,6 @@ ac-library-python is a Python port of [AtCoder Library (ACL)](https://atcoder.jp + maxflow + mincostflow -+ scc + twosat ## Install diff --git a/README_ja.md b/README_ja.md index 0c29fc1..811e765 100644 --- a/README_ja.md +++ b/README_ja.md @@ -21,6 +21,7 @@ ac-library-pythonは、[AtCoder Library (ACL)](https://atcoder.jp/posts/517)のP #### グラフ + [Disjoint Set Union (DSU)](https://github.com/atcoder/ac-library/blob/master/document_ja/dsu.md) ++ scc ### 準備中 @@ -39,7 +40,6 @@ ac-library-pythonは、[AtCoder Library (ACL)](https://atcoder.jp/posts/517)のP + maxflow + mincostflow -+ scc + twosat ## インストール diff --git a/atcoder/_scc.py b/atcoder/_scc.py new file mode 100644 index 0000000..7a4c850 --- /dev/null +++ b/atcoder/_scc.py @@ -0,0 +1,101 @@ +import copy +import sys +import typing + + +class CSR: + def __init__( + self, n: int, edges: typing.List[typing.Tuple[int, int]]) -> None: + self.start = [0] * (n + 1) + self.elist = [0] * len(edges) + + for e in edges: + self.start[e[0] + 1] += 1 + + for i in range(1, n + 1): + self.start[i] += self.start[i - 1] + + counter = copy.deepcopy(self.start) + for e in edges: + self.elist[counter[e[0]]] = e[1] + counter[e[0]] += 1 + + +class SCCGraph: + ''' + Reference: + R. Tarjan, + Depth-First Search and Linear Graph Algorithms + ''' + + def __init__(self, n: int) -> None: + self._n = n + self._edges = [] + + def num_vertices(self) -> int: + return self._n + + def add_edge(self, from_vertex: int, to_vertex: int) -> None: + self._edges.append((from_vertex, to_vertex)) + + def scc_ids(self) -> typing.Tuple[int, typing.List[int]]: + g = CSR(self._n, self._edges) + now_ord = 0 + group_num = 0 + visited = [] + low = [0] * self._n + order = [-1] * self._n + ids = [0] * self._n + + sys.setrecursionlimit(max(self._n + 1000, sys.getrecursionlimit())) + + def dfs(v: int) -> None: + nonlocal now_ord + nonlocal group_num + nonlocal visited + nonlocal low + nonlocal order + nonlocal ids + + low[v] = now_ord + order[v] = now_ord + now_ord += 1 + visited.append(v) + for i in range(g.start[v], g.start[v + 1]): + to = g.elist[i] + if order[to] == -1: + dfs(to) + low[v] = min(low[v], low[to]) + else: + low[v] = min(low[v], order[to]) + + if low[v] == order[v]: + while True: + u = visited[-1] + visited.pop() + order[u] = self._n + ids[u] = group_num + if u == v: + break + group_num += 1 + + for i in range(self._n): + if order[i] == -1: + dfs(i) + + for i in range(self._n): + ids[i] = group_num - 1 - ids[i] + + return group_num, ids + + def scc(self) -> typing.List[typing.List[int]]: + ids = self.scc_ids() + group_num = ids[0] + counts = [0] * group_num + for x in ids[1]: + counts[x] += 1 + groups = [[] for _ in range(group_num)] + for i in range(self._n): + groups[ids[1][i]].append(i) + + return groups diff --git a/atcoder/scc.py b/atcoder/scc.py new file mode 100644 index 0000000..15ba45c --- /dev/null +++ b/atcoder/scc.py @@ -0,0 +1,17 @@ +import typing + +import atcoder._scc + + +class SCCGraph: + def __init__(self, n: int = 0) -> None: + self._internal = atcoder._scc.SCCGraph(n) + + def add_edge(self, from_vertex: int, to_vertex: int) -> None: + n = self._internal.num_vertices() + assert 0 <= from_vertex < n + assert 0 <= to_vertex < n + self._internal.add_edge(from_vertex, to_vertex) + + def scc(self) -> typing.List[typing.List[int]]: + return self._internal.scc() diff --git a/example/scc_practice.py b/example/scc_practice.py new file mode 100644 index 0000000..f75e85a --- /dev/null +++ b/example/scc_practice.py @@ -0,0 +1,27 @@ +# https://atcoder.jp/contests/practice2/tasks/practice2_b + +import sys + +from atcoder.scc import SCCGraph + + +def main() -> None: + n, m = map(int, sys.stdin.readline().split()) + g = SCCGraph(n) + + for i in range(m): + u, v = map(int, sys.stdin.readline().split()) + g.add_edge(u, v) + + scc = g.scc() + + print(len(scc)) + for v in scc: + print(len(v), end='') + for x in v: + print(f' {x}', end='') + print('') + + +if __name__ == '__main__': + main()