Skip to content

three_edge_connected_components.hpp

SECTIONGraph INCLUDEnoya/three_edge_connected_components.hpp

Decompose an undirected multigraph into maximal vertex sets that remain mutually connected after deleting any two edges. The DFS maintains for each active subtree its earliest back-edge endpoint, the net number of upward paths, and a linked chain of unfinished pieces. Whenever a chain can no longer be separated by a cut of size at most two, its circular lists are spliced in O(1). Each vertex and edge enters and leaves a chain once.

Verified by three_edge_connected_components.

把无向图分解为三边连通分量;用于分析删除任意两条边后的连通结构。

Implementation

View on GitHub

#ifndef NOYA_THREE_EDGE_CONNECTED_COMPONENTS_HPP
#define NOYA_THREE_EDGE_CONNECTED_COMPONENTS_HPP 1

/// @complexity Time: O(n + m).
/// Space: O(n + m).

#include <algorithm>
#include <cassert>
#include <functional>
#include <numeric>
#include <vector>

namespace noya {
namespace three_edge_connected_components_internal {

class circular_partition {
  std::vector<int> next_;

public:
  explicit circular_partition(int size) : next_(size) {
    std::iota(next_.begin(), next_.end(), 0);
  }

  void merge(int first, int second) {
    assert(0 <= first && first < int(next_.size()));
    assert(0 <= second && second < int(next_.size()));
    std::swap(next_[first], next_[second]);
  }

  std::vector<std::vector<int>> groups() const {
    std::vector<std::vector<int>> result;
    std::vector<bool> visited(next_.size());
    for (int start = 0; start < int(next_.size()); start++) {
      if (visited[start]) {
        continue;
      }
      result.emplace_back();
      int vertex = start;
      do {
        visited[vertex] = true;
        result.back().push_back(vertex);
        vertex = next_[vertex];
      } while (vertex != start);
    }
    return result;
  }
};

} // namespace three_edge_connected_components_internal

/// @brief Decompose an undirected multigraph into maximal vertex sets that
/// remain mutually connected after deleting any two edges. The DFS maintains
/// for each active subtree its earliest back-edge endpoint, the net number of
/// upward paths, and a linked chain of unfinished pieces. Whenever a chain can
/// no longer be separated by a cut of size at most two, its circular lists are
/// spliced in O(1). Each vertex and edge enters and leaves a chain once.
inline std::vector<std::vector<int>> three_edge_connected_components(
    const std::vector<std::vector<int>> &graph) {
  using three_edge_connected_components_internal::circular_partition;
  int vertex_count = int(graph.size());
  std::vector<int> enter(vertex_count);
  std::vector<int> leave(vertex_count);
  std::vector<int> low(vertex_count, vertex_count);
  std::vector<int> degree(vertex_count);
  std::vector<int> path(vertex_count, vertex_count);
  std::vector<bool> visited(vertex_count);
  circular_partition components(vertex_count);
  int timer = 0;

  auto absorb = [&](int vertex, int child) {
    components.merge(vertex, child);
    degree[vertex] += degree[child];
  };
  std::function<void(int, int)> dfs = [&](int vertex, int parent) {
    visited[vertex] = true;
    enter[vertex] = timer++;
    for (int neighbor : graph[vertex]) {
      if (neighbor == vertex) {
        continue;
      }
      if (neighbor == parent) {
        parent = vertex_count;
        continue;
      }
      if (visited[neighbor]) {
        if (enter[neighbor] < enter[vertex]) {
          degree[vertex]++;
          low[vertex] = std::min(low[vertex], enter[neighbor]);
        } else {
          degree[vertex]--;
          int chain = path[vertex];
          while (chain != vertex_count && enter[chain] <= enter[neighbor] &&
                 enter[neighbor] < leave[chain]) {
            absorb(vertex, chain);
            chain = path[chain];
          }
          path[vertex] = chain;
        }
        continue;
      }
      dfs(neighbor, vertex);
      if (path[neighbor] == vertex_count && degree[neighbor] <= 1) {
        degree[vertex] += degree[neighbor];
        low[vertex] = std::min(low[vertex], low[neighbor]);
        continue;
      }
      if (degree[neighbor] == 0) {
        neighbor = path[neighbor];
      }
      if (low[neighbor] < low[vertex]) {
        low[vertex] = low[neighbor];
        std::swap(neighbor, path[vertex]);
      }
      while (neighbor != vertex_count) {
        absorb(vertex, neighbor);
        neighbor = path[neighbor];
      }
    }
    leave[vertex] = timer;
  };

  for (int vertex = 0; vertex < vertex_count; vertex++) {
    if (!visited[vertex]) {
      dfs(vertex, vertex_count);
    }
  }
  return components.groups();
}

} // namespace noya

#endif // NOYA_THREE_EDGE_CONNECTED_COMPONENTS_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <numeric>
#include <vector>

/// @complexity Time: O(n + m).
/// Space: O(n + m).

namespace noya {
namespace three_edge_connected_components_internal {

class circular_partition {
  std::vector<int> next_;

public:
  explicit circular_partition(int size) : next_(size) {
    std::iota(next_.begin(), next_.end(), 0);
  }

  void merge(int first, int second) {
    assert(0 <= first && first < int(next_.size()));
    assert(0 <= second && second < int(next_.size()));
    std::swap(next_[first], next_[second]);
  }

  std::vector<std::vector<int>> groups() const {
    std::vector<std::vector<int>> result;
    std::vector<bool> visited(next_.size());
    for (int start = 0; start < int(next_.size()); start++) {
      if (visited[start]) {
        continue;
      }
      result.emplace_back();
      int vertex = start;
      do {
        visited[vertex] = true;
        result.back().push_back(vertex);
        vertex = next_[vertex];
      } while (vertex != start);
    }
    return result;
  }
};

} // namespace three_edge_connected_components_internal

/// @brief Decompose an undirected multigraph into maximal vertex sets that
/// remain mutually connected after deleting any two edges. The DFS maintains
/// for each active subtree its earliest back-edge endpoint, the net number of
/// upward paths, and a linked chain of unfinished pieces. Whenever a chain can
/// no longer be separated by a cut of size at most two, its circular lists are
/// spliced in O(1). Each vertex and edge enters and leaves a chain once.
inline std::vector<std::vector<int>> three_edge_connected_components(
    const std::vector<std::vector<int>> &graph) {
  using three_edge_connected_components_internal::circular_partition;
  int vertex_count = int(graph.size());
  std::vector<int> enter(vertex_count);
  std::vector<int> leave(vertex_count);
  std::vector<int> low(vertex_count, vertex_count);
  std::vector<int> degree(vertex_count);
  std::vector<int> path(vertex_count, vertex_count);
  std::vector<bool> visited(vertex_count);
  circular_partition components(vertex_count);
  int timer = 0;

  auto absorb = [&](int vertex, int child) {
    components.merge(vertex, child);
    degree[vertex] += degree[child];
  };
  std::function<void(int, int)> dfs = [&](int vertex, int parent) {
    visited[vertex] = true;
    enter[vertex] = timer++;
    for (int neighbor : graph[vertex]) {
      if (neighbor == vertex) {
        continue;
      }
      if (neighbor == parent) {
        parent = vertex_count;
        continue;
      }
      if (visited[neighbor]) {
        if (enter[neighbor] < enter[vertex]) {
          degree[vertex]++;
          low[vertex] = std::min(low[vertex], enter[neighbor]);
        } else {
          degree[vertex]--;
          int chain = path[vertex];
          while (chain != vertex_count && enter[chain] <= enter[neighbor] &&
                 enter[neighbor] < leave[chain]) {
            absorb(vertex, chain);
            chain = path[chain];
          }
          path[vertex] = chain;
        }
        continue;
      }
      dfs(neighbor, vertex);
      if (path[neighbor] == vertex_count && degree[neighbor] <= 1) {
        degree[vertex] += degree[neighbor];
        low[vertex] = std::min(low[vertex], low[neighbor]);
        continue;
      }
      if (degree[neighbor] == 0) {
        neighbor = path[neighbor];
      }
      if (low[neighbor] < low[vertex]) {
        low[vertex] = low[neighbor];
        std::swap(neighbor, path[vertex]);
      }
      while (neighbor != vertex_count) {
        absorb(vertex, neighbor);
        neighbor = path[neighbor];
      }
    }
    leave[vertex] = timer;
  };

  for (int vertex = 0; vertex < vertex_count; vertex++) {
    if (!visited[vertex]) {
      dfs(vertex, vertex_count);
    }
  }
  return components.groups();
}

} // namespace noya