Skip to content

bipartite_decomposition.hpp

SECTIONGraph INCLUDEnoya/graph/bipartite_decomposition.hpp

二分图最大匹配、最小点覆盖、最大独立集与最小边覆盖;另提供 DAG 原边路径覆盖,以及位集闭包上的最大反链和最小链覆盖。

Complexity: Time: \(O(V + E \sqrt{V})\) for bipartite decomposition / DAG paths. DAG order: \(O(V + (V + E) \lceil V / 64\rceil + R \sqrt{V})\), \(R\) reachable ordered pairs. Space: \(O(V + E)\); DAG order adds \(O(V \lceil V / 64\rceil + R)\).

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(V + E sqrt(V)) for bipartite decomposition / DAG paths.
/// DAG order: O(V + (V+E) ceil(V/64) + R sqrt(V)), R reachable ordered pairs.
/// Space: O(V+E); DAG order adds O(V ceil(V/64) + R).

#include "noya/ds/dynamic_bitset.hpp"

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <span>
#include <utility>
#include <vector>

namespace noya {

struct bipartite_decomposition_result {
  std::vector<int> ml;
  std::vector<int> mr;
  std::vector<int> cvl;
  std::vector<int> cvr;
  std::vector<int> isl;
  std::vector<int> isr;
  bool ok = false;
  std::vector<std::pair<int, int>> ec;

  int matching_size() const {
    return int(
        std::count_if(ml.begin(), ml.end(), [](int val) { return val != -1; }));
  }
};

namespace bipartite_decomposition_internal {

struct adjacency {
  std::vector<std::size_t> off;
  std::vector<int> to;

  adjacency(int nl, int nr, const std::vector<std::pair<int, int>> &es)
      : off(std::size_t(nl) + 1), to(es.size()) {
    for (auto [l, r] : es) {
      assert(0 <= l && l < nl && 0 <= r && r < nr);
      ++off[l + 1];
    }
    for (int l = 0; l < nl; ++l) off[l + 1] += off[l];
    auto cur = off;
    for (auto [l, r] : es) to[cur[l]++] = r;
  }

  explicit adjacency(const std::vector<dynamic_bitset> &rows)
      : off(rows.size() + 1) {
    for (std::size_t u = 0; u < rows.size(); ++u)
      off[u + 1] = off[u] + rows[u].count();
    to.reserve(off.back());
    for (const auto &row : rows)
      for (std::size_t v = row.find_first(); v < row.size(); v = row.find_next(v + 1))
        to.push_back(int(v));
  }

  int size() const { return int(off.size() - 1); }
  std::span<const int> operator[](int u) const {
    return std::span(to).subspan(off[u], off[u + 1] - off[u]);
  }
};

// Return final alternating-reachability distances from unmatched left vertices.
// Per-phase cursors scan each edge at most once during layered augmentation.
inline std::vector<int> match(const adjacency &g, int nr, std::vector<int> &ml,
                              std::vector<int> &mr) {
  int nl = g.size();
  ml.assign(nl, -1);
  mr.assign(nr, -1);
  std::vector<int> active;
  for (int l = 0; l < nl; ++l) {
    if (g[l].empty()) continue;
    active.push_back(l);
    for (int r : g[l]) {
      if (mr[r] == -1) {
        ml[l] = r;
        mr[r] = l;
        break;
      }
    }
  }
  std::vector<int> dis(nl), q;
  std::vector<std::size_t> cur;
  q.reserve(active.size());
  while (true) {
    // Isolates keep distance zero: they are unmatched and trivially reachable.
    // Only O(E) active vertices need resetting/scanning in each matching phase.
    for (int l : active) dis[l] = -1;
    q.clear();
    for (int l : active) {
      if (ml[l] == -1) {
        dis[l] = 0;
        q.push_back(l);
      }
    }
    int limit = -1;
    for (std::size_t head = 0; head < q.size(); ++head) {
      int l = q[head];
      if (limit != -1 && dis[l] >= limit) continue;
      for (int r : g[l]) {
        int next = mr[r];
        if (next == -1) {
          limit = dis[l] + 1;
        } else if (dis[next] == -1) {
          dis[next] = dis[l] + 1;
          q.push_back(next);
        }
      }
    }
    if (limit == -1) return dis;
    cur.resize(nl);
    for (int l : active) cur[l] = 0;
    auto dfs = [&](auto &self, int l) -> bool {
      auto adj = g[l];
      while (cur[l] < adj.size()) {
        int r = adj[cur[l]++], next = mr[r];
        if (next == -1 ? dis[l] + 1 == limit
                      : dis[next] == dis[l] + 1 && dis[next] < limit && self(self, next)) {
          ml[l] = r;
          mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    for (int l : active) if (ml[l] == -1) dfs(dfs, l);
  }
}

inline std::vector<int> topology(const adjacency &g) {
  int n = g.size();
  std::vector<std::size_t> deg(n);
  std::vector<int> top;
  top.reserve(n);
  for (int u = 0; u < n; ++u)
    for (int v : g[u]) ++deg[v];
  for (int u = 0; u < n; ++u)
    if (deg[u] == 0) top.push_back(u);
  for (std::size_t at = 0; at < top.size(); ++at)
    for (int v : g[top[at]])
      if (--deg[v] == 0) top.push_back(v);
  assert(int(top.size()) == n);
  return top;
}

inline std::vector<std::vector<int>> chains(const std::vector<int> &ml,
                                            const std::vector<int> &mr) {
  std::vector<std::vector<int>> res;
  for (int s = 0; s < int(mr.size()); ++s) {
    if (mr[s] != -1) continue;
    res.emplace_back();
    for (int u = s; u != -1; u = ml[u]) res.back().push_back(u);
  }
  return res;
}

} // namespace bipartite_decomposition_internal

/// @brief Maximum matching, Konig minimum vertex cover and its complementary
/// maximum independent set. Also returns a minimum edge cover iff no vertex is
/// isolated (ok=true); otherwise ec is empty. Duplicate edges are allowed.
/// Vertex sets are sorted; matching/edge-cover witnesses need not be canonical.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(nl, nr, es);
  bipartite_decomposition_result res;
  auto dis = match(g, nr, res.ml, res.mr);
  for (int l = 0; l < nl; ++l)
    (dis[l] >= 0 ? res.isl : res.cvl).push_back(l);
  // At maximum matching every reachable right vertex is matched, and its
  // matched left partner is reachable too. No second graph traversal is needed.
  for (int r = 0; r < nr; ++r)
    (res.mr[r] >= 0 && dis[res.mr[r]] >= 0 ? res.cvr : res.isr).push_back(r);

  std::vector<int> first(nr, -1);
  for (auto [l, r] : es)
    if (first[r] == -1) first[r] = l;
  res.ok = true;
  for (int l = 0; l < nl; ++l) res.ok &= !g[l].empty();
  for (int r = 0; r < nr; ++r) res.ok &= first[r] != -1;
  if (res.ok) {
    res.ec.reserve(std::size_t(nl) + nr - res.cvl.size() - res.cvr.size());
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] != -1) res.ec.emplace_back(l, res.ml[l]);
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] == -1) res.ec.emplace_back(l, g[l][0]);
    for (int r = 0; r < nr; ++r)
      if (res.mr[r] == -1) res.ec.emplace_back(first[r], r);
  }
  return res;
}

/// @brief Minimum vertex-disjoint directed path cover of a DAG. Consecutive
/// vertices in a returned path are connected by an original edge. Duplicate
/// edges are allowed; the input must be acyclic.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  topology(g);
  std::vector<int> ml, mr;
  match(g, n, ml, mr);
  return chains(ml, mr);
}

struct dag_order_decomposition_result {
  std::vector<int> ac;
  std::vector<std::vector<int>> cc;
};

/// @brief Maximum reachability antichain and minimum chain cover of a DAG.
/// Chain neighbors need only be reachable, unlike minimum_dag_path_cover.
/// Bitset closure takes O((n+E) ceil(n/64)); matching then takes O(n+R sqrt(n))
/// for R reachable ordered pairs. The input must be acyclic.
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  auto top = topology(g);
  std::vector<dynamic_bitset> reach(n, dynamic_bitset(n));
  for (auto it = top.rbegin(); it != top.rend(); ++it) {
    for (int v : g[*it]) {
      if (reach[*it][v]) continue;
      reach[*it].set(v);
      reach[*it] |= reach[v];
    }
  }
  adjacency comparable(reach);
  std::vector<int> ml, mr;
  auto dis = match(comparable, n, ml, mr);
  dag_order_decomposition_result res;
  for (int u = 0; u < n; ++u)
    if (dis[u] >= 0 && (mr[u] == -1 || dis[mr[u]] < 0)) res.ac.push_back(u);
  res.cc = chains(ml, mr);
  return res;
}

} // namespace noya

 // NOYA_BIPARTITE_DECOMPOSITION_HPP
#ifndef NOYA_BIPARTITE_DECOMPOSITION_HPP
#define NOYA_BIPARTITE_DECOMPOSITION_HPP 1

/// @complexity Time: O(V + E sqrt(V)) for bipartite decomposition / DAG paths.
/// DAG order: O(V + (V+E) ceil(V/64) + R sqrt(V)), R reachable ordered pairs.
/// Space: O(V+E); DAG order adds O(V ceil(V/64) + R).

#include "noya/ds/dynamic_bitset.hpp"

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <span>
#include <utility>
#include <vector>

namespace noya {

struct bipartite_decomposition_result {
  std::vector<int> ml;
  std::vector<int> mr;
  std::vector<int> cvl;
  std::vector<int> cvr;
  std::vector<int> isl;
  std::vector<int> isr;
  bool ok = false;
  std::vector<std::pair<int, int>> ec;

  int matching_size() const {
    return int(
        std::count_if(ml.begin(), ml.end(), [](int val) { return val != -1; }));
  }
};

namespace bipartite_decomposition_internal {

struct adjacency {
  std::vector<std::size_t> off;
  std::vector<int> to;

  adjacency(int nl, int nr, const std::vector<std::pair<int, int>> &es)
      : off(std::size_t(nl) + 1), to(es.size()) {
    for (auto [l, r] : es) {
      assert(0 <= l && l < nl && 0 <= r && r < nr);
      ++off[l + 1];
    }
    for (int l = 0; l < nl; ++l) off[l + 1] += off[l];
    auto cur = off;
    for (auto [l, r] : es) to[cur[l]++] = r;
  }

  explicit adjacency(const std::vector<dynamic_bitset> &rows)
      : off(rows.size() + 1) {
    for (std::size_t u = 0; u < rows.size(); ++u)
      off[u + 1] = off[u] + rows[u].count();
    to.reserve(off.back());
    for (const auto &row : rows)
      for (std::size_t v = row.find_first(); v < row.size(); v = row.find_next(v + 1))
        to.push_back(int(v));
  }

  int size() const { return int(off.size() - 1); }
  std::span<const int> operator[](int u) const {
    return std::span(to).subspan(off[u], off[u + 1] - off[u]);
  }
};

// Return final alternating-reachability distances from unmatched left vertices.
// Per-phase cursors scan each edge at most once during layered augmentation.
inline std::vector<int> match(const adjacency &g, int nr, std::vector<int> &ml,
                              std::vector<int> &mr) {
  int nl = g.size();
  ml.assign(nl, -1);
  mr.assign(nr, -1);
  std::vector<int> active;
  for (int l = 0; l < nl; ++l) {
    if (g[l].empty()) continue;
    active.push_back(l);
    for (int r : g[l]) {
      if (mr[r] == -1) {
        ml[l] = r;
        mr[r] = l;
        break;
      }
    }
  }
  std::vector<int> dis(nl), q;
  std::vector<std::size_t> cur;
  q.reserve(active.size());
  while (true) {
    // Isolates keep distance zero: they are unmatched and trivially reachable.
    // Only O(E) active vertices need resetting/scanning in each matching phase.
    for (int l : active) dis[l] = -1;
    q.clear();
    for (int l : active) {
      if (ml[l] == -1) {
        dis[l] = 0;
        q.push_back(l);
      }
    }
    int limit = -1;
    for (std::size_t head = 0; head < q.size(); ++head) {
      int l = q[head];
      if (limit != -1 && dis[l] >= limit) continue;
      for (int r : g[l]) {
        int next = mr[r];
        if (next == -1) {
          limit = dis[l] + 1;
        } else if (dis[next] == -1) {
          dis[next] = dis[l] + 1;
          q.push_back(next);
        }
      }
    }
    if (limit == -1) return dis;
    cur.resize(nl);
    for (int l : active) cur[l] = 0;
    auto dfs = [&](auto &self, int l) -> bool {
      auto adj = g[l];
      while (cur[l] < adj.size()) {
        int r = adj[cur[l]++], next = mr[r];
        if (next == -1 ? dis[l] + 1 == limit
                      : dis[next] == dis[l] + 1 && dis[next] < limit && self(self, next)) {
          ml[l] = r;
          mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    for (int l : active) if (ml[l] == -1) dfs(dfs, l);
  }
}

inline std::vector<int> topology(const adjacency &g) {
  int n = g.size();
  std::vector<std::size_t> deg(n);
  std::vector<int> top;
  top.reserve(n);
  for (int u = 0; u < n; ++u)
    for (int v : g[u]) ++deg[v];
  for (int u = 0; u < n; ++u)
    if (deg[u] == 0) top.push_back(u);
  for (std::size_t at = 0; at < top.size(); ++at)
    for (int v : g[top[at]])
      if (--deg[v] == 0) top.push_back(v);
  assert(int(top.size()) == n);
  return top;
}

inline std::vector<std::vector<int>> chains(const std::vector<int> &ml,
                                            const std::vector<int> &mr) {
  std::vector<std::vector<int>> res;
  for (int s = 0; s < int(mr.size()); ++s) {
    if (mr[s] != -1) continue;
    res.emplace_back();
    for (int u = s; u != -1; u = ml[u]) res.back().push_back(u);
  }
  return res;
}

} // namespace bipartite_decomposition_internal

/// @brief Maximum matching, Konig minimum vertex cover and its complementary
/// maximum independent set. Also returns a minimum edge cover iff no vertex is
/// isolated (ok=true); otherwise ec is empty. Duplicate edges are allowed.
/// Vertex sets are sorted; matching/edge-cover witnesses need not be canonical.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(nl, nr, es);
  bipartite_decomposition_result res;
  auto dis = match(g, nr, res.ml, res.mr);
  for (int l = 0; l < nl; ++l)
    (dis[l] >= 0 ? res.isl : res.cvl).push_back(l);
  // At maximum matching every reachable right vertex is matched, and its
  // matched left partner is reachable too. No second graph traversal is needed.
  for (int r = 0; r < nr; ++r)
    (res.mr[r] >= 0 && dis[res.mr[r]] >= 0 ? res.cvr : res.isr).push_back(r);

  std::vector<int> first(nr, -1);
  for (auto [l, r] : es)
    if (first[r] == -1) first[r] = l;
  res.ok = true;
  for (int l = 0; l < nl; ++l) res.ok &= !g[l].empty();
  for (int r = 0; r < nr; ++r) res.ok &= first[r] != -1;
  if (res.ok) {
    res.ec.reserve(std::size_t(nl) + nr - res.cvl.size() - res.cvr.size());
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] != -1) res.ec.emplace_back(l, res.ml[l]);
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] == -1) res.ec.emplace_back(l, g[l][0]);
    for (int r = 0; r < nr; ++r)
      if (res.mr[r] == -1) res.ec.emplace_back(first[r], r);
  }
  return res;
}

/// @brief Minimum vertex-disjoint directed path cover of a DAG. Consecutive
/// vertices in a returned path are connected by an original edge. Duplicate
/// edges are allowed; the input must be acyclic.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  topology(g);
  std::vector<int> ml, mr;
  match(g, n, ml, mr);
  return chains(ml, mr);
}

struct dag_order_decomposition_result {
  std::vector<int> ac;
  std::vector<std::vector<int>> cc;
};

/// @brief Maximum reachability antichain and minimum chain cover of a DAG.
/// Chain neighbors need only be reachable, unlike minimum_dag_path_cover.
/// Bitset closure takes O((n+E) ceil(n/64)); matching then takes O(n+R sqrt(n))
/// for R reachable ordered pairs. The input must be acyclic.
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  auto top = topology(g);
  std::vector<dynamic_bitset> reach(n, dynamic_bitset(n));
  for (auto it = top.rbegin(); it != top.rend(); ++it) {
    for (int v : g[*it]) {
      if (reach[*it][v]) continue;
      reach[*it].set(v);
      reach[*it] |= reach[v];
    }
  }
  adjacency comparable(reach);
  std::vector<int> ml, mr;
  auto dis = match(comparable, n, ml, mr);
  dag_order_decomposition_result res;
  for (int u = 0; u < n; ++u)
    if (dis[u] >= 0 && (mr[u] == -1 || dis[mr[u]] < 0)) res.ac.push_back(u);
  res.cc = chains(ml, mr);
  return res;
}

} // namespace noya

#endif // NOYA_BIPARTITE_DECOMPOSITION_HPP
/// @complexity Time: O(V + E sqrt(V)) for bipartite decomposition / DAG paths.
/// DAG order: O(V + (V+E) ceil(V/64) + R sqrt(V)), R reachable ordered pairs.
/// Space: O(V+E); DAG order adds O(V ceil(V/64) + R).

/// @complexity Time: O(n / 64) for whole-bitset operations; O(1) bit access.
/// Space: O(n / 64).

#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace noya {

/// @brief Resizable packed bitset with bitwise operations, shifts, population
/// count, and efficient iteration over set bits.
struct dynamic_bitset {
  using word_type = std::uint64_t;
  static constexpr std::size_t W = 64;

  std::size_t sz_ = 0;
  std::vector<word_type> a;

  dynamic_bitset() = default;
  explicit dynamic_bitset(std::size_t siz, bool val = false)
      : sz_(siz), a(word_count(siz), val ? ~word_type{} : 0) {
    trim();
  }

  std::size_t size() const { return sz_; }
  bool empty() const { return sz_ == 0; }

  bool test(std::size_t pos) const {
    assert(pos < sz_);
    return (a[pos / W] >> (pos % W)) & 1;
  }

  bool operator[](std::size_t pos) const { return test(pos); }

  dynamic_bitset &set(std::size_t pos, bool val = true) {
    assert(pos < sz_);
    word_type msk = word_type(1) << (pos % W);
    if (val) {
      a[pos / W] |= msk;
    } else {
      a[pos / W] &= ~msk;
    }
    return *this;
  }

  dynamic_bitset &reset(std::size_t pos) { return set(pos, false); }

  dynamic_bitset &flip(std::size_t pos) {
    assert(pos < sz_);
    a[pos / W] ^= word_type(1) << (pos % W);
    return *this;
  }

  dynamic_bitset &set() {
    std::fill(a.begin(), a.end(), ~word_type{});
    trim();
    return *this;
  }

  dynamic_bitset &reset() {
    std::fill(a.begin(), a.end(), word_type{});
    return *this;
  }

  dynamic_bitset &flip() {
    for (word_type &w : a) {
      w = ~w;
    }
    trim();
    return *this;
  }

  std::size_t count() const {
    std::size_t res = 0;
    for (word_type w : a) {
      res += std::popcount(w);
    }
    return res;
  }

  bool any() const {
    return std::any_of(a.begin(), a.end(), [](word_type w) { return w != 0; });
  }

  bool none() const { return !any(); }

  /// @brief Return the first set position at least position, or size() if no
  /// such position exists.
  std::size_t find_next(std::size_t pos) const {
    if (pos >= sz_) {
      return sz_;
    }
    std::size_t idx = pos / W;
    word_type w = a[idx] & (~word_type{} << (pos % W));
    if (w != 0) {
      return std::min(sz_, idx * W + std::size_t(std::countr_zero(w)));
    }
    for (idx++; idx < a.size(); idx++) {
      if (a[idx] != 0) {
        return std::min(sz_, idx * W + std::size_t(std::countr_zero(a[idx])));
      }
    }
    return sz_;
  }

  std::size_t find_first() const { return find_next(0); }

  dynamic_bitset &operator&=(const dynamic_bitset &rhs) {
    check_same_size(rhs);
    for (std::size_t i = 0; i < a.size(); i++) {
      a[i] &= rhs.a[i];
    }
    return *this;
  }

  dynamic_bitset &operator|=(const dynamic_bitset &rhs) {
    check_same_size(rhs);
    for (std::size_t i = 0; i < a.size(); i++) {
      a[i] |= rhs.a[i];
    }
    return *this;
  }

  dynamic_bitset &operator^=(const dynamic_bitset &rhs) {
    check_same_size(rhs);
    for (std::size_t i = 0; i < a.size(); i++) {
      a[i] ^= rhs.a[i];
    }
    return *this;
  }

  dynamic_bitset &operator<<=(std::size_t k) {
    if (k == 0)
      return *this;
    if (k >= sz_)
      return reset();
    std::size_t blk = k / W;
    int rem = int(k % W);
    if (rem == 0) {
      std::move_backward(a.begin(), a.end() - blk, a.end());
    } else {
      for (std::size_t i = a.size() - 1; i > blk; --i)
        a[i] = (a[i - blk] << rem) | (a[i - blk - 1] >> (W - rem));
      a[blk] = a[0] << rem;
    }
    std::fill(a.begin(), a.begin() + blk, word_type{});
    trim();
    return *this;
  }

  dynamic_bitset &operator>>=(std::size_t k) {
    if (k == 0)
      return *this;
    if (k >= sz_)
      return reset();
    std::size_t blk = k / W;
    std::size_t count = a.size() - blk;
    int rem = int(k % W);
    if (rem == 0) {
      std::move(a.begin() + blk, a.end(), a.begin());
    } else {
      for (std::size_t i = 0; i + 1 < count; ++i)
        a[i] = (a[i + blk] >> rem) | (a[i + blk + 1] << (W - rem));
      a[count - 1] = a.back() >> rem;
    }
    std::fill(a.begin() + count, a.end(), word_type{});
    return *this;
  }

  friend dynamic_bitset operator&(dynamic_bitset arr, const dynamic_bitset &b) {
    arr &= b;
    return arr;
  }
  friend dynamic_bitset operator|(dynamic_bitset arr, const dynamic_bitset &b) {
    arr |= b;
    return arr;
  }
  friend dynamic_bitset operator^(dynamic_bitset arr, const dynamic_bitset &b) {
    arr ^= b;
    return arr;
  }
  friend dynamic_bitset operator<<(dynamic_bitset val, std::size_t k) {
    val <<= k;
    return val;
  }
  friend dynamic_bitset operator>>(dynamic_bitset val, std::size_t k) {
    val >>= k;
    return val;
  }
  friend dynamic_bitset operator~(dynamic_bitset val) {
    val.flip();
    return val;
  }

  friend bool operator==(const dynamic_bitset &,
                         const dynamic_bitset &) = default;

private:
  static std::size_t word_count(std::size_t siz) { return (siz + W - 1) / W; }

  void trim() {
    if (!a.empty() && sz_ % W != 0) {
      a.back() &= (word_type(1) << (sz_ % W)) - word_type(1);
    }
  }

  void check_same_size(const dynamic_bitset &rhs) const {
    assert(sz_ == rhs.sz_);
  }
};

} // namespace noya

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <span>
#include <utility>
#include <vector>

namespace noya {

struct bipartite_decomposition_result {
  std::vector<int> ml;
  std::vector<int> mr;
  std::vector<int> cvl;
  std::vector<int> cvr;
  std::vector<int> isl;
  std::vector<int> isr;
  bool ok = false;
  std::vector<std::pair<int, int>> ec;

  int matching_size() const {
    return int(
        std::count_if(ml.begin(), ml.end(), [](int val) { return val != -1; }));
  }
};

namespace bipartite_decomposition_internal {

struct adjacency {
  std::vector<std::size_t> off;
  std::vector<int> to;

  adjacency(int nl, int nr, const std::vector<std::pair<int, int>> &es)
      : off(std::size_t(nl) + 1), to(es.size()) {
    for (auto [l, r] : es) {
      assert(0 <= l && l < nl && 0 <= r && r < nr);
      ++off[l + 1];
    }
    for (int l = 0; l < nl; ++l) off[l + 1] += off[l];
    auto cur = off;
    for (auto [l, r] : es) to[cur[l]++] = r;
  }

  explicit adjacency(const std::vector<dynamic_bitset> &rows)
      : off(rows.size() + 1) {
    for (std::size_t u = 0; u < rows.size(); ++u)
      off[u + 1] = off[u] + rows[u].count();
    to.reserve(off.back());
    for (const auto &row : rows)
      for (std::size_t v = row.find_first(); v < row.size(); v = row.find_next(v + 1))
        to.push_back(int(v));
  }

  int size() const { return int(off.size() - 1); }
  std::span<const int> operator[](int u) const {
    return std::span(to).subspan(off[u], off[u + 1] - off[u]);
  }
};

// Return final alternating-reachability distances from unmatched left vertices.
// Per-phase cursors scan each edge at most once during layered augmentation.
inline std::vector<int> match(const adjacency &g, int nr, std::vector<int> &ml,
                              std::vector<int> &mr) {
  int nl = g.size();
  ml.assign(nl, -1);
  mr.assign(nr, -1);
  std::vector<int> active;
  for (int l = 0; l < nl; ++l) {
    if (g[l].empty()) continue;
    active.push_back(l);
    for (int r : g[l]) {
      if (mr[r] == -1) {
        ml[l] = r;
        mr[r] = l;
        break;
      }
    }
  }
  std::vector<int> dis(nl), q;
  std::vector<std::size_t> cur;
  q.reserve(active.size());
  while (true) {
    // Isolates keep distance zero: they are unmatched and trivially reachable.
    // Only O(E) active vertices need resetting/scanning in each matching phase.
    for (int l : active) dis[l] = -1;
    q.clear();
    for (int l : active) {
      if (ml[l] == -1) {
        dis[l] = 0;
        q.push_back(l);
      }
    }
    int limit = -1;
    for (std::size_t head = 0; head < q.size(); ++head) {
      int l = q[head];
      if (limit != -1 && dis[l] >= limit) continue;
      for (int r : g[l]) {
        int next = mr[r];
        if (next == -1) {
          limit = dis[l] + 1;
        } else if (dis[next] == -1) {
          dis[next] = dis[l] + 1;
          q.push_back(next);
        }
      }
    }
    if (limit == -1) return dis;
    cur.resize(nl);
    for (int l : active) cur[l] = 0;
    auto dfs = [&](auto &self, int l) -> bool {
      auto adj = g[l];
      while (cur[l] < adj.size()) {
        int r = adj[cur[l]++], next = mr[r];
        if (next == -1 ? dis[l] + 1 == limit
                      : dis[next] == dis[l] + 1 && dis[next] < limit && self(self, next)) {
          ml[l] = r;
          mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    for (int l : active) if (ml[l] == -1) dfs(dfs, l);
  }
}

inline std::vector<int> topology(const adjacency &g) {
  int n = g.size();
  std::vector<std::size_t> deg(n);
  std::vector<int> top;
  top.reserve(n);
  for (int u = 0; u < n; ++u)
    for (int v : g[u]) ++deg[v];
  for (int u = 0; u < n; ++u)
    if (deg[u] == 0) top.push_back(u);
  for (std::size_t at = 0; at < top.size(); ++at)
    for (int v : g[top[at]])
      if (--deg[v] == 0) top.push_back(v);
  assert(int(top.size()) == n);
  return top;
}

inline std::vector<std::vector<int>> chains(const std::vector<int> &ml,
                                            const std::vector<int> &mr) {
  std::vector<std::vector<int>> res;
  for (int s = 0; s < int(mr.size()); ++s) {
    if (mr[s] != -1) continue;
    res.emplace_back();
    for (int u = s; u != -1; u = ml[u]) res.back().push_back(u);
  }
  return res;
}

} // namespace bipartite_decomposition_internal

/// @brief Maximum matching, Konig minimum vertex cover and its complementary
/// maximum independent set. Also returns a minimum edge cover iff no vertex is
/// isolated (ok=true); otherwise ec is empty. Duplicate edges are allowed.
/// Vertex sets are sorted; matching/edge-cover witnesses need not be canonical.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(nl, nr, es);
  bipartite_decomposition_result res;
  auto dis = match(g, nr, res.ml, res.mr);
  for (int l = 0; l < nl; ++l)
    (dis[l] >= 0 ? res.isl : res.cvl).push_back(l);
  // At maximum matching every reachable right vertex is matched, and its
  // matched left partner is reachable too. No second graph traversal is needed.
  for (int r = 0; r < nr; ++r)
    (res.mr[r] >= 0 && dis[res.mr[r]] >= 0 ? res.cvr : res.isr).push_back(r);

  std::vector<int> first(nr, -1);
  for (auto [l, r] : es)
    if (first[r] == -1) first[r] = l;
  res.ok = true;
  for (int l = 0; l < nl; ++l) res.ok &= !g[l].empty();
  for (int r = 0; r < nr; ++r) res.ok &= first[r] != -1;
  if (res.ok) {
    res.ec.reserve(std::size_t(nl) + nr - res.cvl.size() - res.cvr.size());
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] != -1) res.ec.emplace_back(l, res.ml[l]);
    for (int l = 0; l < nl; ++l)
      if (res.ml[l] == -1) res.ec.emplace_back(l, g[l][0]);
    for (int r = 0; r < nr; ++r)
      if (res.mr[r] == -1) res.ec.emplace_back(first[r], r);
  }
  return res;
}

/// @brief Minimum vertex-disjoint directed path cover of a DAG. Consecutive
/// vertices in a returned path are connected by an original edge. Duplicate
/// edges are allowed; the input must be acyclic.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  topology(g);
  std::vector<int> ml, mr;
  match(g, n, ml, mr);
  return chains(ml, mr);
}

struct dag_order_decomposition_result {
  std::vector<int> ac;
  std::vector<std::vector<int>> cc;
};

/// @brief Maximum reachability antichain and minimum chain cover of a DAG.
/// Chain neighbors need only be reachable, unlike minimum_dag_path_cover.
/// Bitset closure takes O((n+E) ceil(n/64)); matching then takes O(n+R sqrt(n))
/// for R reachable ordered pairs. The input must be acyclic.
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  using namespace bipartite_decomposition_internal;
  adjacency g(n, n, es);
  auto top = topology(g);
  std::vector<dynamic_bitset> reach(n, dynamic_bitset(n));
  for (auto it = top.rbegin(); it != top.rend(); ++it) {
    for (int v : g[*it]) {
      if (reach[*it][v]) continue;
      reach[*it].set(v);
      reach[*it] |= reach[v];
    }
  }
  adjacency comparable(reach);
  std::vector<int> ml, mr;
  auto dis = match(comparable, n, ml, mr);
  dag_order_decomposition_result res;
  for (int u = 0; u < n; ++u)
    if (dis[u] >= 0 && (mr[u] == -1 || dis[mr[u]] < 0)) res.ac.push_back(u);
  res.cc = chains(ml, mr);
  return res;
}

} // namespace noya

 // NOYA_BIPARTITE_DECOMPOSITION_HPP