AtCoder problem 021

C++ solution:

Crystal solution: # ac-library.cr by hakatashi https://github.com/google/ac-library.cr
#
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module AtCoder
  # Implements [atcoder::scc_graph](https://atcoder.github.io/ac-library/master/document_en/scc.html).
  #
  # ```
  # scc = AtCoder::SCC.new(3_i64)
  # scc.add_edge(0, 1)
  # scc.add_edge(1, 0)
  # scc.add_edge(2, 0)
  # scc.scc # => [Set{2}, Set{0, 1}]
  # ```
  class SCC
    alias Adjacency = NamedTuple(in: Array(Int64), out: Array(Int64))

    getter size : Int64
    getter adjacencies : Array(Adjacency)

    def initialize(@size)
      @adjacencies = Array(Adjacency).new(@size) { {in: [] of Int64, out: [] of Int64} }

      @topological_order = Array(Int64).new(@size)
      @visit_counts = Array(Int64).new(@size, 0_i64)
      @visited = Set(Int64).new
      @stack = Deque(Int64).new
      @groups = Array(Set(Int64)).new
    end

    # Implements atcoder::scc_graph.add_edge(from, to).
    def add_edge(from, to)
      @adjacencies[from][:out] << to.to_i64
      @adjacencies[to][:in] << from.to_i64
    end

    private def dfs(start)
      @stack << start
      @visited << start

      until @stack.empty?
        node = @stack.last
        children = @adjacencies[node][:out]

        if @visit_counts[node] < children.size
          child = children[@visit_counts[node]]
          @visit_counts[node] += 1

          unless @visited.includes?(child)
            @visited << child
            @stack << child
          end
        else
          @topological_order << node
          @stack.pop
        end
      end
    end

    private def reverse_dfs(start)
      @stack << start
      @visited << start
      group = Set{start}

      until @stack.empty?
        node = @stack.pop
        children = @adjacencies[node][:in]

        children.each do |child|
          unless @visited.includes?(child)
            @stack << child
            @visited << child
            group << child
          end
        end
      end

      @groups << group
    end

    # Implements atcoder::scc_graph.scc().
    def scc
      @visited = Set(Int64).new
      @stack = Deque(Int64).new
      @visit_counts = Array(Int64).new(@size, 0_i64)
      @topological_order = Array(Int64).new(@size)
      @groups = Array(Set(Int64)).new

      @size.times do |node|
        unless @visited.includes?(node)
          dfs(node)
        end
      end

      @visited = Set(Int64).new

      @topological_order.reverse_each do |node|
        unless @visited.includes?(node)
          reverse_dfs(node)
        end
      end

      @groups
    end
  end
end

n, m = read_line.split.map(&.to_i)
scc = AtCoder::SCC.new(n.to_i64)
m.times do
  a, b = read_line.split.map(&.to_i.pred)
  scc.add_edge(a, b)
end
puts scc.scc.sum { |group|
  i = group.size.to_i64
  i * i.pred // 2
}


Python solution: import sys
input = sys.stdin.readline

class SCC:
    def __init__(self, n):
        self.n = n
        self.graph = [[] for _ in range(n)]
        self.rev_graph = [[] for _ in range(n)]
        self.labels = [-1] * n
        self.lb_cnt = 0

    def add_edge(self, v, nxt_v):
        self.graph[v].append(nxt_v)
        self.rev_graph[nxt_v].append(v)

    def build(self):
        self.post_order = []
        self.used = [False] * self.n
        for v in range(self.n):
            if not self.used[v]:
                self._dfs(v)
        for v in reversed(self.post_order):
            if self.labels[v] == -1:
                self._rev_dfs(v)
                self.lb_cnt += 1

    def _dfs(self, v):
        stack = [v, 0]
        while stack:
            v, idx = stack[-2:]
            if not idx and self.used[v]:
                stack.pop()
                stack.pop()
            else:
                self.used[v] = True
                if idx < len(self.graph[v]):
                    stack[-1] += 1
                    stack.append(self.graph[v][idx])
                    stack.append(0)
                else:
                    stack.pop()
                    self.post_order.append(stack.pop())

    def _rev_dfs(self, v):
        stack = [v]
        self.labels[v] = self.lb_cnt
        while stack:
            v = stack.pop()
            for nxt_v in self.rev_graph[v]:
                if self.labels[nxt_v] != -1:
                    continue
                stack.append(nxt_v)
                self.labels[nxt_v] = self.lb_cnt

    def construct(self):
        self.dag = [[] for i in range(self.lb_cnt)]
        self.groups = [[] for i in range(self.lb_cnt)]
        for v, lb in enumerate(self.labels):
            for nxt_v in self.graph[v]:
                nxt_lb = self.labels[nxt_v]
                if lb == nxt_lb:
                    continue
                self.dag[lb].append(nxt_lb)
            self.groups[lb].append(v)
        return self.dag, self.groups

n, m = map(int, input().split())
graph = [list(map(int,input().split())) for _ in range(m)]
scc = SCC(n)

for u, v in graph:
    scc.add_edge(u - 1, v - 1)
scc.build()
_,elems = scc.construct()

ans = 0
for i in range(len(elems)):
    ans += len(elems[i]) * (len(elems[i]) - 1) // 2

print(ans)