Skip to content

undirected_girth.hpp

SECTIONGraph INCLUDEnoya/graph/undirected_girth.hpp

求无权无向多重图的一条最短环,返回沿环排列的边编号;森林返回空数组,自环与重边分别形成长度 1、2 的环。先剥叶再逐顶点 BFS,最坏 O(\(n\)(\(n\)+\(m\))),森林 O(\(n\)+\(m\));并列最短环不固定起点或方向。

Complexity: Time: \(O(V (V + E))\) worst case; \(O(V + E)\) for forests. Space: \(O(V + E)\).

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(V(V + E)) worst case; O(V + E) for forests.
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <limits>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return edge ids in cyclic order for any shortest undirected simple cycle.
/// Self-loops and parallel-edge 2-cycles count; forests return an empty vector.
/// Input is unchanged; tied cycles have no fixed choice or orientation.
/// Require 0 <= n < INT_MAX, valid endpoints, and at most INT_MAX edges.
inline std::vector<int>
undirected_girth(int n, const std::vector<std::pair<int, int>> &es) {
  assert(0 <= n && n < std::numeric_limits<int>::max());
  assert(es.size() <= std::size_t(std::numeric_limits<int>::max()));
  if (es.empty()) return {};
  std::vector<int> degree(n);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    if (a == b) return {id};
    ++degree[a];
    ++degree[b];
  }
  std::vector<std::vector<std::pair<int, int>>> g(n);
  for (int u = 0; u < n; ++u) g[u].reserve(degree[u]);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    g[a].emplace_back(b, id);
    g[b].emplace_back(a, id);
  }
  { // Rule out all 2-cycles before allowing a triangle to terminate the search.
    std::vector<int> seen(n, -1), first(n);
    for (int u = 0; u < n; ++u) {
      for (auto [v, id] : g[u]) {
        if (seen[v] == u) return {first[v], id};
        seen[v] = u;
        first[v] = id;
      }
    }
  }
  std::vector<int> q;
  q.reserve(n);
  for (int u = 0; u < n; ++u)
    if (degree[u] < 2) q.push_back(u);
  for (int at = 0; at < int(q.size()); ++at) {
    int u = q[at];
    degree[u] = 0;
    for (auto [v, id] : g[u])
      if (degree[v] > 0 && --degree[v] == 1) q.push_back(v);
  }
  if (q.size() == std::size_t(n)) return {};

  std::vector<int> d(n), parent(n), pe(n), best;
  int bound = n + 1;
  for (int s = 0; s < n; ++s) {
    if (degree[s] < 2) continue;
    std::fill(d.begin(), d.end(), -1);
    d[s] = 0;
    pe[s] = -1;
    q.clear();
    q.push_back(s);
    for (int at = 0; at < int(q.size()); ++at) {
      int u = q[at];
      if (2LL * d[u] >= bound) break;
      for (auto [v, id] : g[u]) {
        if (degree[v] < 2) continue;
        if (d[v] < 0) {
          d[v] = d[u] + 1;
          parent[v] = u;
          pe[v] = id;
          q.push_back(v);
        } else if (id != pe[u] && id != pe[v] &&
                   1LL * d[u] + d[v] + 1 < bound) {
          // Remove the shared tree prefix so the witness has no repeated vertex.
          int a = u, b = v;
          std::vector<int> left, right;
          while (d[a] > d[b]) {
            left.push_back(pe[a]);
            a = parent[a];
          }
          while (d[b] > d[a]) {
            right.push_back(pe[b]);
            b = parent[b];
          }
          while (a != b) {
            left.push_back(pe[a]);
            a = parent[a];
            right.push_back(pe[b]);
            b = parent[b];
          }
          left.insert(left.end(), right.rbegin(), right.rend());
          left.push_back(id);
          best = std::move(left);
          bound = int(best.size());
          if (bound == 3) return best;
        }
      }
    }
  }
  return best;
}

} // namespace noya

 // NOYA_UNDIRECTED_GIRTH_HPP
#ifndef NOYA_UNDIRECTED_GIRTH_HPP
#define NOYA_UNDIRECTED_GIRTH_HPP 1

/// @complexity Time: O(V(V + E)) worst case; O(V + E) for forests.
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <limits>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return edge ids in cyclic order for any shortest undirected simple cycle.
/// Self-loops and parallel-edge 2-cycles count; forests return an empty vector.
/// Input is unchanged; tied cycles have no fixed choice or orientation.
/// Require 0 <= n < INT_MAX, valid endpoints, and at most INT_MAX edges.
inline std::vector<int>
undirected_girth(int n, const std::vector<std::pair<int, int>> &es) {
  assert(0 <= n && n < std::numeric_limits<int>::max());
  assert(es.size() <= std::size_t(std::numeric_limits<int>::max()));
  if (es.empty()) return {};
  std::vector<int> degree(n);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    if (a == b) return {id};
    ++degree[a];
    ++degree[b];
  }
  std::vector<std::vector<std::pair<int, int>>> g(n);
  for (int u = 0; u < n; ++u) g[u].reserve(degree[u]);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    g[a].emplace_back(b, id);
    g[b].emplace_back(a, id);
  }
  { // Rule out all 2-cycles before allowing a triangle to terminate the search.
    std::vector<int> seen(n, -1), first(n);
    for (int u = 0; u < n; ++u) {
      for (auto [v, id] : g[u]) {
        if (seen[v] == u) return {first[v], id};
        seen[v] = u;
        first[v] = id;
      }
    }
  }
  std::vector<int> q;
  q.reserve(n);
  for (int u = 0; u < n; ++u)
    if (degree[u] < 2) q.push_back(u);
  for (int at = 0; at < int(q.size()); ++at) {
    int u = q[at];
    degree[u] = 0;
    for (auto [v, id] : g[u])
      if (degree[v] > 0 && --degree[v] == 1) q.push_back(v);
  }
  if (q.size() == std::size_t(n)) return {};

  std::vector<int> d(n), parent(n), pe(n), best;
  int bound = n + 1;
  for (int s = 0; s < n; ++s) {
    if (degree[s] < 2) continue;
    std::fill(d.begin(), d.end(), -1);
    d[s] = 0;
    pe[s] = -1;
    q.clear();
    q.push_back(s);
    for (int at = 0; at < int(q.size()); ++at) {
      int u = q[at];
      if (2LL * d[u] >= bound) break;
      for (auto [v, id] : g[u]) {
        if (degree[v] < 2) continue;
        if (d[v] < 0) {
          d[v] = d[u] + 1;
          parent[v] = u;
          pe[v] = id;
          q.push_back(v);
        } else if (id != pe[u] && id != pe[v] &&
                   1LL * d[u] + d[v] + 1 < bound) {
          // Remove the shared tree prefix so the witness has no repeated vertex.
          int a = u, b = v;
          std::vector<int> left, right;
          while (d[a] > d[b]) {
            left.push_back(pe[a]);
            a = parent[a];
          }
          while (d[b] > d[a]) {
            right.push_back(pe[b]);
            b = parent[b];
          }
          while (a != b) {
            left.push_back(pe[a]);
            a = parent[a];
            right.push_back(pe[b]);
            b = parent[b];
          }
          left.insert(left.end(), right.rbegin(), right.rend());
          left.push_back(id);
          best = std::move(left);
          bound = int(best.size());
          if (bound == 3) return best;
        }
      }
    }
  }
  return best;
}

} // namespace noya

#endif // NOYA_UNDIRECTED_GIRTH_HPP
/// @complexity Time: O(V(V + E)) worst case; O(V + E) for forests.
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <limits>
#include <utility>
#include <vector>

namespace noya {

/// @brief Return edge ids in cyclic order for any shortest undirected simple cycle.
/// Self-loops and parallel-edge 2-cycles count; forests return an empty vector.
/// Input is unchanged; tied cycles have no fixed choice or orientation.
/// Require 0 <= n < INT_MAX, valid endpoints, and at most INT_MAX edges.
inline std::vector<int>
undirected_girth(int n, const std::vector<std::pair<int, int>> &es) {
  assert(0 <= n && n < std::numeric_limits<int>::max());
  assert(es.size() <= std::size_t(std::numeric_limits<int>::max()));
  if (es.empty()) return {};
  std::vector<int> degree(n);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    if (a == b) return {id};
    ++degree[a];
    ++degree[b];
  }
  std::vector<std::vector<std::pair<int, int>>> g(n);
  for (int u = 0; u < n; ++u) g[u].reserve(degree[u]);
  for (int id = 0; id < int(es.size()); ++id) {
    auto [a, b] = es[id];
    g[a].emplace_back(b, id);
    g[b].emplace_back(a, id);
  }
  { // Rule out all 2-cycles before allowing a triangle to terminate the search.
    std::vector<int> seen(n, -1), first(n);
    for (int u = 0; u < n; ++u) {
      for (auto [v, id] : g[u]) {
        if (seen[v] == u) return {first[v], id};
        seen[v] = u;
        first[v] = id;
      }
    }
  }
  std::vector<int> q;
  q.reserve(n);
  for (int u = 0; u < n; ++u)
    if (degree[u] < 2) q.push_back(u);
  for (int at = 0; at < int(q.size()); ++at) {
    int u = q[at];
    degree[u] = 0;
    for (auto [v, id] : g[u])
      if (degree[v] > 0 && --degree[v] == 1) q.push_back(v);
  }
  if (q.size() == std::size_t(n)) return {};

  std::vector<int> d(n), parent(n), pe(n), best;
  int bound = n + 1;
  for (int s = 0; s < n; ++s) {
    if (degree[s] < 2) continue;
    std::fill(d.begin(), d.end(), -1);
    d[s] = 0;
    pe[s] = -1;
    q.clear();
    q.push_back(s);
    for (int at = 0; at < int(q.size()); ++at) {
      int u = q[at];
      if (2LL * d[u] >= bound) break;
      for (auto [v, id] : g[u]) {
        if (degree[v] < 2) continue;
        if (d[v] < 0) {
          d[v] = d[u] + 1;
          parent[v] = u;
          pe[v] = id;
          q.push_back(v);
        } else if (id != pe[u] && id != pe[v] &&
                   1LL * d[u] + d[v] + 1 < bound) {
          // Remove the shared tree prefix so the witness has no repeated vertex.
          int a = u, b = v;
          std::vector<int> left, right;
          while (d[a] > d[b]) {
            left.push_back(pe[a]);
            a = parent[a];
          }
          while (d[b] > d[a]) {
            right.push_back(pe[b]);
            b = parent[b];
          }
          while (a != b) {
            left.push_back(pe[a]);
            a = parent[a];
            right.push_back(pe[b]);
            b = parent[b];
          }
          left.insert(left.end(), right.rbegin(), right.rend());
          left.push_back(id);
          best = std::move(left);
          bound = int(best.size());
          if (bound == 3) return best;
        }
      }
    }
  }
  return best;
}

} // namespace noya

 // NOYA_UNDIRECTED_GIRTH_HPP