Problem solution · Python

ABC357 E — Reachability in Functional Graph

ABC357 E — Reachability in Functional Graph: a Python solution using depth-first search. Learn the idea, check the complexity, and read the full code, with credit to KATO-Hiro AtCoder Solutions.

Technique
Depth-first search
Source
KATO-Hiro AtCoder Solutions
Length
164 lines
Start with the idea.

Try the problem first. If you get stuck, read the approach below, then write your own solution. The full code is at the bottom.

Approach

Depth-first search

For ABC357 E — Reachability in Functional Graph, the implementation follows one branch at a time, making it suitable for components, trees, backtracking, or dependency exploration.

  1. Define the state carried into one recursive or stack frame.
  2. Mark or choose the current state before exploring children.
  3. Combine child results or undo the choice when the branch finishes.

Code notes

  • 164 lines of Python from the credited upstream file abc357_e.py.
  • The implementation visibly relies on sequence storage, hash lookup, ordered lookup.
  • No explicit loop blocks detected, together with recursive traversal.

Complexity

Count unique states for graph traversal; for backtracking, count the branching factor and maximum depth.

Check the problem constraints before deciding whether this complexity will pass.

Source

Code and credit

This code comes from KATO-Hiro AtCoder Solutions by KATO-Hiro and is used under the CC0-1.0 licence.

Full codeABC357 E — Reachability in Functional Graph · PythonPython
Use this to learn the idea, then write your own version.
# -*- coding: utf-8 -*- import sysimport typing  # See:# https://github.com/not522/ac-library-python/tree/master/atcoderclass SCCGraph:    def __init__(self, n: int = 0) -> None:        self._internal = _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()  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 = self.start.copy()         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: typing.List[typing.Tuple[int, int]] = []     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: typing.List[typing.List[int]] = [[] for _ in range(group_num)]         for i in range(self._n):            groups[ids[1][i]].append(i)         return groups  def main():    import sys     input = sys.stdin.readline     n = int(input())    a = list(map(lambda x: int(x) - 1, input().split()))    g = SCCGraph(n)     # functional graph    # 強連結成分分解を利用して、サイクルを見つける    for i, ai in enumerate(a):        g.add_edge(i, ai)     counts = [0] * n     # サイクルは末尾にあるので、逆順に処理    for group in g.scc()[::-1]:        size = len(group)        g0 = group[0]         # サイクル or 自己ループ        if size >= 2 or g0 == a[g0]:            for g in group:                counts[g] = size        else:            # サイクルに近い頂点から + 1            counts[g0] = counts[a[g0]] + 1     print(sum(counts))  if __name__ == "__main__":    main() 

Did this explanation save you time? I'm a Grade 11 student building this free library to make difficult algorithms easier to understand.

Buy me a coffee ↗