Skip to content

eulerian_trail.hpp

SECTIONGraph INCLUDEnoya/graph/eulerian_trail.hpp

共用连续邻接表的欧拉迹核心,保留独立返回类型和原接口,构造有向/无向多重图的顶点与输入边编号序列。

Complexity: Time: \(O(V + E)\). Space: \(O(V + E)\), including the result. Empty-edge inputs take \(O(1)\) time and space.

AC 记录:eulerian_trail_directed, eulerian_trail_undirected。

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(V + E). Space: O(V + E), including the result.
/// Empty-edge inputs take O(1) time and space.

#include "noya/graph/euler_walk.hpp"
#include <cassert>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

/// @brief Vertex sequence and input edge ids of an Eulerian trail.
/// Kept distinct from euler_walk_result for source compatibility.
struct eulerian_trail_result {
  std::vector<int> vs;
  std::vector<int> eid;
};

/// @brief Construct a directed Eulerian trail using every input edge once, or
/// return nullopt when none exists from s. Require n>=0, -1<=s<n, valid
/// endpoints and an int-representable edge count. Self-loops/parallel edges
/// are allowed. s=-1 selects the unique out-in=1 vertex, or the smallest
/// nonisolated vertex for a circuit. Adjacency choices follow input order;
/// inputs are unchanged. With no edges return {s} (default 0), or empty
/// vectors for n=0, s=-1. Share the iterative CSR core with euler_walk.hpp.
inline std::optional<eulerian_trail_result>
directed_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                        int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_directed(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

/// @brief Undirected counterpart, with the same preconditions and empty-graph
/// convention. s=-1 chooses the smallest odd vertex for an open trail, or the
/// smallest nonisolated vertex for a circuit. Either odd endpoint is a valid
/// explicit start. Reuse the core without copying its result vectors.
inline std::optional<eulerian_trail_result>
undirected_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                          int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_undirected(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

} // namespace noya

 // NOYA_EULERIAN_TRAIL_HPP
#ifndef NOYA_EULERIAN_TRAIL_HPP
#define NOYA_EULERIAN_TRAIL_HPP 1

/// @complexity Time: O(V + E). Space: O(V + E), including the result.
/// Empty-edge inputs take O(1) time and space.

#include "noya/graph/euler_walk.hpp"
#include <cassert>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

/// @brief Vertex sequence and input edge ids of an Eulerian trail.
/// Kept distinct from euler_walk_result for source compatibility.
struct eulerian_trail_result {
  std::vector<int> vs;
  std::vector<int> eid;
};

/// @brief Construct a directed Eulerian trail using every input edge once, or
/// return nullopt when none exists from s. Require n>=0, -1<=s<n, valid
/// endpoints and an int-representable edge count. Self-loops/parallel edges
/// are allowed. s=-1 selects the unique out-in=1 vertex, or the smallest
/// nonisolated vertex for a circuit. Adjacency choices follow input order;
/// inputs are unchanged. With no edges return {s} (default 0), or empty
/// vectors for n=0, s=-1. Share the iterative CSR core with euler_walk.hpp.
inline std::optional<eulerian_trail_result>
directed_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                        int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_directed(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

/// @brief Undirected counterpart, with the same preconditions and empty-graph
/// convention. s=-1 chooses the smallest odd vertex for an open trail, or the
/// smallest nonisolated vertex for a circuit. Either odd endpoint is a valid
/// explicit start. Reuse the core without copying its result vectors.
inline std::optional<eulerian_trail_result>
undirected_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                          int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_undirected(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

} // namespace noya

#endif // NOYA_EULERIAN_TRAIL_HPP
/// @complexity Time: O(V + E). Space: O(V + E), including the result.
/// Empty-edge inputs take O(1) time and space.

/// @complexity Time: O(V + E). Space: O(V + E), including the result.
/// Empty-edge inputs take O(1) time and space.

#include <cassert>
#include <cstddef>
#include <limits>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

struct euler_walk_result {
  std::vector<int> vs;
  std::vector<int> eid;
};

namespace euler_walk_internal {

template <bool Directed>
std::optional<euler_walk_result>
solve(int n, const std::vector<std::pair<int, int>> &es, int s) {
  assert(n >= 0);
  if (n == 0) {
    return es.empty() && s == -1
               ? std::optional<euler_walk_result>(euler_walk_result{})
               : std::nullopt;
  }
  if (s < -1 || s >= n) {
    return std::nullopt;
  }
  if (es.empty()) {
    return euler_walk_result{{s == -1 ? 0 : s}, {}};
  }
  assert(es.size() <= std::size_t(std::numeric_limits<int>::max()));
  int m = int(es.size());
  std::vector<std::size_t> off(std::size_t(n) + 1);
  int first = -1, start = -1, end = -1, odd = 0;
  {
    std::vector<int> balance(Directed ? n : 0);
    for (auto [u, v] : es) {
      assert(0 <= u && u < n && 0 <= v && v < n);
      ++off[u];
      if constexpr (Directed) {
        ++balance[u];
        --balance[v];
      } else {
        ++off[v];
      }
    }
    for (int u = 0; u < n; ++u) {
      if (off[u] && first == -1) {
        first = u;
      }
      if constexpr (Directed) {
        if (balance[u] == 1 && start == -1) {
          start = u;
        } else if (balance[u] == -1 && end == -1) {
          end = u;
        } else if (balance[u] != 0) {
          return std::nullopt;
        }
      } else if (off[u] & 1) {
        if (++odd > 2) {
          return std::nullopt;
        }
        if (start == -1) {
          start = u;
        }
      }
    }
    if constexpr (Directed) {
      if ((start == -1) != (end == -1) ||
          (s != -1 && ((start != -1 && s != start) || off[s] == 0))) {
        return std::nullopt;
      }
    } else if ((odd != 0 && odd != 2) ||
               (s != -1 && ((odd && !(off[s] & 1)) || off[s] == 0))) {
      return std::nullopt;
    }
  }
  if (s == -1) {
    s = start == -1 ? first : start;
  }

  // Counts become end offsets. Reverse filling turns them into start offsets
  // and preserves the input edge order in every adjacency row.
  for (int u = 0; u < n; ++u) {
    off[u + 1] += off[u];
  }
  std::vector<std::pair<int, int>> adj(off[n]);
  for (int id = m; id-- > 0;) {
    auto [u, v] = es[id];
    adj[--off[u]] = {v, id};
    if constexpr (!Directed) {
      adj[--off[v]] = {u, id};
    }
  }
  std::vector<std::size_t> ptr(off.begin(), off.end() - 1);
  std::vector<char> used(Directed ? 0 : m);
  euler_walk_result res;
  res.vs.resize(es.size() + 1);
  res.eid.resize(es.size());

  // The prefix is the DFS stack; the suffix is the finished forward answer.
  // write-top equals the number of unused edges, so the regions never clash.
  std::size_t top = 0, write = es.size();
  res.vs[0] = s;
  for (;;) {
    assert(top <= write);
    int u = res.vs[top];
    if constexpr (!Directed) {
      while (ptr[u] < off[u + 1] && used[adj[ptr[u]].second]) {
        ++ptr[u];
      }
    }
    if (ptr[u] < off[u + 1]) {
      auto [v, id] = adj[ptr[u]++];
      if constexpr (!Directed) {
        used[id] = true;
      }
      assert(top < write);
      res.eid[top] = id;
      res.vs[++top] = v;
    } else {
      res.vs[write] = u;
      if (top == 0) {
        break;
      }
      res.eid[write - 1] = res.eid[top - 1];
      --top;
      --write;
    }
  }
  if (write != 0) {
    return std::nullopt; // Some edges lie outside the visited component.
  }
  return res;
}

} // namespace euler_walk_internal

/// @brief Find an Euler trail in an undirected multigraph, or nullopt if none
/// exists from the requested start. Self-loops and parallel edges are allowed.
/// Require n>=0, valid endpoints, and an int-representable edge count.
/// s=-1 chooses the smallest odd vertex, or the smallest nonisolated vertex
/// for a circuit. Other invalid starts return nullopt. Inputs are unchanged;
/// adjacency choices follow input order, preserving the original exact trail.
/// With no edges, return {s} (default 0), or empty vectors when n=0 and s=-1.
inline std::optional<euler_walk_result>
euler_walk_undirected(int n, const std::vector<std::pair<int, int>> &es,
                      int s = -1) {
  return euler_walk_internal::solve<false>(n, es, s);
}

/// @brief Directed counterpart, with the same empty/start conventions.
/// s=-1 selects the unique out-in=1 vertex for an open trail, otherwise the
/// smallest vertex with an outgoing edge. Each edge has only one adjacency
/// entry, so no visited-edge array is needed in the directed specialization.
inline std::optional<euler_walk_result>
euler_walk_directed(int n, const std::vector<std::pair<int, int>> &es,
                    int s = -1) {
  return euler_walk_internal::solve<true>(n, es, s);
}

} // namespace noya

#include <cassert>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

/// @brief Vertex sequence and input edge ids of an Eulerian trail.
/// Kept distinct from euler_walk_result for source compatibility.
struct eulerian_trail_result {
  std::vector<int> vs;
  std::vector<int> eid;
};

/// @brief Construct a directed Eulerian trail using every input edge once, or
/// return nullopt when none exists from s. Require n>=0, -1<=s<n, valid
/// endpoints and an int-representable edge count. Self-loops/parallel edges
/// are allowed. s=-1 selects the unique out-in=1 vertex, or the smallest
/// nonisolated vertex for a circuit. Adjacency choices follow input order;
/// inputs are unchanged. With no edges return {s} (default 0), or empty
/// vectors for n=0, s=-1. Share the iterative CSR core with euler_walk.hpp.
inline std::optional<eulerian_trail_result>
directed_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                        int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_directed(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

/// @brief Undirected counterpart, with the same preconditions and empty-graph
/// convention. s=-1 chooses the smallest odd vertex for an open trail, or the
/// smallest nonisolated vertex for a circuit. Either odd endpoint is a valid
/// explicit start. Reuse the core without copying its result vectors.
inline std::optional<eulerian_trail_result>
undirected_eulerian_trail(int n, const std::vector<std::pair<int, int>> &es,
                          int s = -1) {
  assert(n >= 0);
  assert(-1 <= s && s < n);
  auto res = euler_walk_undirected(n, es, s);
  if (!res) {
    return std::nullopt;
  }
  return eulerian_trail_result{std::move(res->vs), std::move(res->eid)};
}

} // namespace noya

 // NOYA_EULERIAN_TRAIL_HPP