Skip to content

incremental_scc.hpp

SECTIONGraph INCLUDEnoya/incremental_scc.hpp

Return a certificate time for every edge in an incremental graph. Divide the prefix-time interval at its midpoint and compute SCCs using the edges already present there. Edges internal to one SCC recurse into the earlier half; all other edges recurse into the later half after contracting those SCCs. Thus every recursion level is linear in its active graph. An edge with returned time t joins its endpoint components after the first t insertions; applying all such joins reconstructs every SCC partition. edge_count+1 denotes an edge that never joins two distinct components.

Verified by incremental_scc.

离线处理按顺序加入有向边后的强连通变化;适合询问两点最早何时进入同一 SCC。

Implementation

View on GitHub

#ifndef NOYA_INCREMENTAL_SCC_HPP
#define NOYA_INCREMENTAL_SCC_HPP 1

/// @complexity Time: O((V + E) log E).
/// Space: O(V + E log E).

#include "atcoder/scc.hpp"

#include <algorithm>
#include <cassert>
#include <functional>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return a certificate time for every edge in an incremental graph.
/// Divide the prefix-time interval at its midpoint and compute SCCs using the
/// edges already present there. Edges internal to one SCC recurse into the
/// earlier half; all other edges recurse into the later half after contracting
/// those SCCs. Thus every recursion level is linear in its active graph. An
/// edge with returned time t joins its endpoint components after the first t
/// insertions; applying all such joins reconstructs every SCC partition.
/// `edge_count+1` denotes an edge that never joins two distinct components.
inline std::vector<int>
incremental_scc_merge_times(int vertex_count,
                            const std::vector<std::pair<int, int>> &edges) {
  assert(vertex_count >= 0);
  int edge_count = int(edges.size());
  for (auto [from, to] : edges) {
    assert(0 <= from && from < vertex_count);
    assert(0 <= to && to < vertex_count);
  }
  std::vector<int> merge_time(edge_count, edge_count + 1);
  std::vector<std::tuple<int, int, int>> active;
  active.reserve(edge_count);
  for (int id = 0; id < edge_count; id++) {
    active.emplace_back(id, edges[id].first, edges[id].second);
  }
  std::vector<int> new_index(vertex_count, -1);

  auto divide = [&](auto &self, std::vector<std::tuple<int, int, int>> data,
                    int left_time, int right_time) -> void {
    if (data.empty() || right_time == left_time + 1) {
      return;
    }
    int middle_time = (left_time + right_time) / 2;
    int compressed_size = 0;
    for (auto [id, from, to] : data) {
      if (new_index[from] == -1) {
        new_index[from] = compressed_size++;
      }
      if (new_index[to] == -1) {
        new_index[to] = compressed_size++;
      }
    }

    atcoder::scc_graph graph(compressed_size);
    for (auto [id, from, to] : data) {
      if (id < middle_time) {
        graph.add_edge(new_index[from], new_index[to]);
      }
    }
    auto components = graph.scc();
    std::vector<int> component_of(compressed_size);
    for (int component = 0; component < int(components.size()); component++) {
      for (int vertex : components[component]) {
        component_of[vertex] = component;
      }
    }

    std::vector<std::tuple<int, int, int>> earlier;
    std::vector<std::tuple<int, int, int>> later;
    earlier.reserve(data.size());
    later.reserve(data.size());
    for (auto [id, original_from, original_to] : data) {
      int from = new_index[original_from];
      int to = new_index[original_to];
      if (id < middle_time && component_of[from] == component_of[to]) {
        merge_time[id] = std::min(merge_time[id], middle_time);
        earlier.emplace_back(id, from, to);
      } else {
        later.emplace_back(id, component_of[from], component_of[to]);
      }
    }
    for (auto [id, from, to] : data) {
      new_index[from] = -1;
      new_index[to] = -1;
    }
    self(self, std::move(earlier), left_time, middle_time);
    self(self, std::move(later), middle_time, right_time);
  };
  divide(divide, std::move(active), 0, edge_count + 1);
  return merge_time;
}

/// @brief After every insertion, return the sum of weight products over pairs
/// of vertices in the same strongly connected component.
template <class T>
std::vector<T> incremental_scc_pair_product_sums(
    const std::vector<T> &vertex_weights,
    const std::vector<std::pair<int, int>> &edges) {
  int vertex_count = int(vertex_weights.size());
  int edge_count = int(edges.size());
  auto merge_time = incremental_scc_merge_times(vertex_count, edges);
  std::vector<std::vector<int>> joins(edge_count + 1);
  for (int id = 0; id < edge_count; id++) {
    if (merge_time[id] <= edge_count) {
      joins[merge_time[id]].push_back(id);
    }
  }

  std::vector<int> parent(vertex_count, -1);
  std::vector<T> component_sum = vertex_weights;
  auto leader = [&](auto &self, int vertex) -> int {
    if (parent[vertex] < 0) {
      return vertex;
    }
    return parent[vertex] = self(self, parent[vertex]);
  };
  T total{};
  std::vector<T> result(edge_count);
  for (int time = 1; time <= edge_count; time++) {
    for (int id : joins[time]) {
      int first = leader(leader, edges[id].first);
      int second = leader(leader, edges[id].second);
      if (first == second) {
        continue;
      }
      total += component_sum[first] * component_sum[second];
      if (parent[first] > parent[second]) {
        std::swap(first, second);
      }
      parent[first] += parent[second];
      parent[second] = first;
      component_sum[first] += component_sum[second];
    }
    result[time - 1] = total;
  }
  return result;
}

} // namespace noya

#endif // NOYA_INCREMENTAL_SCC_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O((V + E) log E).
/// Space: O(V + E log E).

namespace atcoder {
namespace internal {

template <class E> struct csr {
    std::vector<int> start;
    std::vector<E> elist;
    explicit csr(int n, const std::vector<std::pair<int, E>>& edges)
        : start(n + 1), elist(edges.size()) {
        for (auto e : edges) {
            start[e.first + 1]++;
        }
        for (int i = 1; i <= n; i++) {
            start[i] += start[i - 1];
        }
        auto counter = start;
        for (auto e : edges) {
            elist[counter[e.first]++] = e.second;
        }
    }
};

}  // namespace internal

}  // namespace atcoder

namespace atcoder {
namespace internal {

// Reference:
// R. Tarjan,
// Depth-First Search and Linear Graph Algorithms
struct scc_graph {
  public:
    explicit scc_graph(int n) : _n(n) {}

    int num_vertices() { return _n; }

    void add_edge(int from, int to) { edges.push_back({from, {to}}); }

    // @return pair of (# of scc, scc id)
    std::pair<int, std::vector<int>> scc_ids() {
        auto g = csr<edge>(_n, edges);
        int now_ord = 0, group_num = 0;
        std::vector<int> visited, low(_n), ord(_n, -1), ids(_n);
        visited.reserve(_n);
        auto dfs = [&](auto self, int v) -> void {
            low[v] = ord[v] = now_ord++;
            visited.push_back(v);
            for (int i = g.start[v]; i < g.start[v + 1]; i++) {
                auto to = g.elist[i].to;
                if (ord[to] == -1) {
                    self(self, to);
                    low[v] = std::min(low[v], low[to]);
                } else {
                    low[v] = std::min(low[v], ord[to]);
                }
            }
            if (low[v] == ord[v]) {
                while (true) {
                    int u = visited.back();
                    visited.pop_back();
                    ord[u] = _n;
                    ids[u] = group_num;
                    if (u == v) break;
                }
                group_num++;
            }
        };
        for (int i = 0; i < _n; i++) {
            if (ord[i] == -1) dfs(dfs, i);
        }
        for (auto& x : ids) {
            x = group_num - 1 - x;
        }
        return {group_num, ids};
    }

    std::vector<std::vector<int>> scc() {
        auto ids = scc_ids();
        int group_num = ids.first;
        std::vector<int> counts(group_num);
        for (auto x : ids.second) counts[x]++;
        std::vector<std::vector<int>> groups(ids.first);
        for (int i = 0; i < group_num; i++) {
            groups[i].reserve(counts[i]);
        }
        for (int i = 0; i < _n; i++) {
            groups[ids.second[i]].push_back(i);
        }
        return groups;
    }

  private:
    int _n;
    struct edge {
        int to;
    };
    std::vector<std::pair<int, edge>> edges;
};

}  // namespace internal

}  // namespace atcoder

namespace atcoder {

struct scc_graph {
  public:
    scc_graph() : internal(0) {}
    explicit scc_graph(int n) : internal(n) {}

    void add_edge(int from, int to) {
        int n = internal.num_vertices();
        assert(0 <= from && from < n);
        assert(0 <= to && to < n);
        internal.add_edge(from, to);
    }

    std::vector<std::vector<int>> scc() { return internal.scc(); }

  private:
    internal::scc_graph internal;
};

}  // namespace atcoder

namespace noya {

/// @brief Return a certificate time for every edge in an incremental graph.
/// Divide the prefix-time interval at its midpoint and compute SCCs using the
/// edges already present there. Edges internal to one SCC recurse into the
/// earlier half; all other edges recurse into the later half after contracting
/// those SCCs. Thus every recursion level is linear in its active graph. An
/// edge with returned time t joins its endpoint components after the first t
/// insertions; applying all such joins reconstructs every SCC partition.
/// `edge_count+1` denotes an edge that never joins two distinct components.
inline std::vector<int>
incremental_scc_merge_times(int vertex_count,
                            const std::vector<std::pair<int, int>> &edges) {
  assert(vertex_count >= 0);
  int edge_count = int(edges.size());
  for (auto [from, to] : edges) {
    assert(0 <= from && from < vertex_count);
    assert(0 <= to && to < vertex_count);
  }
  std::vector<int> merge_time(edge_count, edge_count + 1);
  std::vector<std::tuple<int, int, int>> active;
  active.reserve(edge_count);
  for (int id = 0; id < edge_count; id++) {
    active.emplace_back(id, edges[id].first, edges[id].second);
  }
  std::vector<int> new_index(vertex_count, -1);

  auto divide = [&](auto &self, std::vector<std::tuple<int, int, int>> data,
                    int left_time, int right_time) -> void {
    if (data.empty() || right_time == left_time + 1) {
      return;
    }
    int middle_time = (left_time + right_time) / 2;
    int compressed_size = 0;
    for (auto [id, from, to] : data) {
      if (new_index[from] == -1) {
        new_index[from] = compressed_size++;
      }
      if (new_index[to] == -1) {
        new_index[to] = compressed_size++;
      }
    }

    atcoder::scc_graph graph(compressed_size);
    for (auto [id, from, to] : data) {
      if (id < middle_time) {
        graph.add_edge(new_index[from], new_index[to]);
      }
    }
    auto components = graph.scc();
    std::vector<int> component_of(compressed_size);
    for (int component = 0; component < int(components.size()); component++) {
      for (int vertex : components[component]) {
        component_of[vertex] = component;
      }
    }

    std::vector<std::tuple<int, int, int>> earlier;
    std::vector<std::tuple<int, int, int>> later;
    earlier.reserve(data.size());
    later.reserve(data.size());
    for (auto [id, original_from, original_to] : data) {
      int from = new_index[original_from];
      int to = new_index[original_to];
      if (id < middle_time && component_of[from] == component_of[to]) {
        merge_time[id] = std::min(merge_time[id], middle_time);
        earlier.emplace_back(id, from, to);
      } else {
        later.emplace_back(id, component_of[from], component_of[to]);
      }
    }
    for (auto [id, from, to] : data) {
      new_index[from] = -1;
      new_index[to] = -1;
    }
    self(self, std::move(earlier), left_time, middle_time);
    self(self, std::move(later), middle_time, right_time);
  };
  divide(divide, std::move(active), 0, edge_count + 1);
  return merge_time;
}

/// @brief After every insertion, return the sum of weight products over pairs
/// of vertices in the same strongly connected component.
template <class T>
std::vector<T> incremental_scc_pair_product_sums(
    const std::vector<T> &vertex_weights,
    const std::vector<std::pair<int, int>> &edges) {
  int vertex_count = int(vertex_weights.size());
  int edge_count = int(edges.size());
  auto merge_time = incremental_scc_merge_times(vertex_count, edges);
  std::vector<std::vector<int>> joins(edge_count + 1);
  for (int id = 0; id < edge_count; id++) {
    if (merge_time[id] <= edge_count) {
      joins[merge_time[id]].push_back(id);
    }
  }

  std::vector<int> parent(vertex_count, -1);
  std::vector<T> component_sum = vertex_weights;
  auto leader = [&](auto &self, int vertex) -> int {
    if (parent[vertex] < 0) {
      return vertex;
    }
    return parent[vertex] = self(self, parent[vertex]);
  };
  T total{};
  std::vector<T> result(edge_count);
  for (int time = 1; time <= edge_count; time++) {
    for (int id : joins[time]) {
      int first = leader(leader, edges[id].first);
      int second = leader(leader, edges[id].second);
      if (first == second) {
        continue;
      }
      total += component_sum[first] * component_sum[second];
      if (parent[first] > parent[second]) {
        std::swap(first, second);
      }
      parent[first] += parent[second];
      parent[second] = first;
      component_sum[first] += component_sum[second];
    }
    result[time - 1] = total;
  }
  return result;
}

} // namespace noya