Skip to content

bipartite_decomposition.hpp

SECTIONGraph INCLUDEnoya/bipartite_decomposition.hpp

把二分图的匹配结构分解成 Dulmage–Mendelsohn 块;用于刻画哪些顶点可被最大匹配覆盖。

Complexity: Time: O(E sqrt(V)) for matching/cover; DAG reductions add their constructed edges. Space: O(V + E).

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(E sqrt(V)) for matching/cover; DAG reductions add their constructed edges.
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <queue>
#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; }));
  }
};

/// @brief Hopcroft-Karp matching with Konig minimum vertex cover, maximum
/// independent set, and a minimum edge cover when the graph has no isolates.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  std::vector<std::vector<int>> g(nl);
  std::vector<std::vector<int>> rg(nr);
  for (auto [l, r] : es) {
    assert(0 <= l && l < nl);
    assert(0 <= r && r < nr);
    g[l].push_back(r);
    rg[r].push_back(l);
  }
  for (auto &adj : g) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }
  for (auto &adj : rg) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }

  bipartite_decomposition_result res;
  res.ml.assign(nl, -1);
  res.mr.assign(nr, -1);
  std::vector<int> dis(nl);
  while (true) {
    std::queue<int> q;
    std::fill(dis.begin(), dis.end(), -1);
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        dis[l] = 0;
        q.push(l);
      }
    }
    while (!q.empty()) {
      int l = q.front();
      q.pop();
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt != -1 && dis[nxt] == -1) {
          dis[nxt] = dis[l] + 1;
          q.push(nxt);
        }
      }
    }
    auto aug = [&](auto &self, int l) -> bool {
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt == -1 || (dis[nxt] == dis[l] + 1 && self(self, nxt))) {
          res.ml[l] = r;
          res.mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    int au1 = 0;
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        au1 += aug(aug, l);
      }
    }
    if (au1 == 0) {
      break;
    }
  }

  std::vector<bool> rl(nl);
  std::vector<bool> rr(nr);
  std::queue<int> q;
  for (int l = 0; l < nl; l++) {
    if (res.ml[l] == -1) {
      rl[l] = true;
      q.push(l);
    }
  }
  while (!q.empty()) {
    int l = q.front();
    q.pop();
    for (int r : g[l]) {
      if (res.ml[l] == r || rr[r]) {
        continue;
      }
      rr[r] = true;
      int nxt = res.mr[r];
      if (nxt != -1 && !rl[nxt]) {
        rl[nxt] = true;
        q.push(nxt);
      }
    }
  }
  for (int l = 0; l < nl; l++) {
    if (rl[l]) {
      res.isl.push_back(l);
    } else {
      res.cvl.push_back(l);
    }
  }
  for (int r = 0; r < nr; r++) {
    if (rr[r]) {
      res.cvr.push_back(r);
    } else {
      res.isr.push_back(r);
    }
  }

  res.ok = true;
  for (int l = 0; l < nl; l++) {
    res.ok &= !g[l].empty();
    if (res.ml[l] != -1) {
      res.ec.emplace_back(l, res.ml[l]);
    }
  }
  for (int r = 0; r < nr; r++) {
    res.ok &= !rg[r].empty();
  }
  if (res.ok) {
    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(rg[r][0], r);
      }
    }
  } else {
    res.ec.clear();
  }
  return res;
}

/// @brief Return a minimum vertex-disjoint directed path cover of a DAG using
/// bipartite matching; edges inside each returned path are original graph
/// edges.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<int>> g(n);
  std::vector<int> deg(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  int cnt = 0;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    cnt++;
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(cnt == n);

  auto dec = bipartite_decomposition(n, n, es);
  std::vector<std::vector<int>> pth;
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    pth.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      pth.back().push_back(u);
    }
  }
  return pth;
}

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 via
/// transitive closure and Dilworth's theorem in O(n^3).
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<bool>> vis(n, std::vector<bool>(n));
  std::vector<int> deg(n);
  std::vector<std::vector<int>> g(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
    vis[v][to] = true;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  std::vector<int> top;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    top.push_back(u);
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(int(top.size()) == n);
  for (int mid : top) {
    for (int v = 0; v < n; v++) {
      if (vis[v][mid]) {
        for (int to = 0; to < n; to++) {
          vis[v][to] = vis[v][to] || vis[mid][to];
        }
      }
    }
  }
  std::vector<std::pair<int, int>> cmp;
  for (int v = 0; v < n; v++) {
    for (int to = 0; to < n; to++) {
      if (vis[v][to]) {
        cmp.emplace_back(v, to);
      }
    }
  }
  auto dec = bipartite_decomposition(n, n, cmp);
  std::vector<bool> cl(n), cr(n);
  for (int u : dec.cvl) {
    cl[u] = true;
  }
  for (int u : dec.cvr) {
    cr[u] = true;
  }
  dag_order_decomposition_result res;
  for (int u = 0; u < n; u++) {
    if (!cl[u] && !cr[u]) {
      res.ac.push_back(u);
    }
  }
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    res.cc.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      res.cc.back().push_back(u);
    }
  }
  return res;
}

} // namespace noya
#ifndef NOYA_BIPARTITE_DECOMPOSITION_HPP
#define NOYA_BIPARTITE_DECOMPOSITION_HPP 1

/// @complexity Time: O(E sqrt(V)) for matching/cover; DAG reductions add their constructed edges.
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <queue>
#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; }));
  }
};

/// @brief Hopcroft-Karp matching with Konig minimum vertex cover, maximum
/// independent set, and a minimum edge cover when the graph has no isolates.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  std::vector<std::vector<int>> g(nl);
  std::vector<std::vector<int>> rg(nr);
  for (auto [l, r] : es) {
    assert(0 <= l && l < nl);
    assert(0 <= r && r < nr);
    g[l].push_back(r);
    rg[r].push_back(l);
  }
  for (auto &adj : g) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }
  for (auto &adj : rg) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }

  bipartite_decomposition_result res;
  res.ml.assign(nl, -1);
  res.mr.assign(nr, -1);
  std::vector<int> dis(nl);
  while (true) {
    std::queue<int> q;
    std::fill(dis.begin(), dis.end(), -1);
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        dis[l] = 0;
        q.push(l);
      }
    }
    while (!q.empty()) {
      int l = q.front();
      q.pop();
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt != -1 && dis[nxt] == -1) {
          dis[nxt] = dis[l] + 1;
          q.push(nxt);
        }
      }
    }
    auto aug = [&](auto &self, int l) -> bool {
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt == -1 || (dis[nxt] == dis[l] + 1 && self(self, nxt))) {
          res.ml[l] = r;
          res.mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    int au1 = 0;
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        au1 += aug(aug, l);
      }
    }
    if (au1 == 0) {
      break;
    }
  }

  std::vector<bool> rl(nl);
  std::vector<bool> rr(nr);
  std::queue<int> q;
  for (int l = 0; l < nl; l++) {
    if (res.ml[l] == -1) {
      rl[l] = true;
      q.push(l);
    }
  }
  while (!q.empty()) {
    int l = q.front();
    q.pop();
    for (int r : g[l]) {
      if (res.ml[l] == r || rr[r]) {
        continue;
      }
      rr[r] = true;
      int nxt = res.mr[r];
      if (nxt != -1 && !rl[nxt]) {
        rl[nxt] = true;
        q.push(nxt);
      }
    }
  }
  for (int l = 0; l < nl; l++) {
    if (rl[l]) {
      res.isl.push_back(l);
    } else {
      res.cvl.push_back(l);
    }
  }
  for (int r = 0; r < nr; r++) {
    if (rr[r]) {
      res.cvr.push_back(r);
    } else {
      res.isr.push_back(r);
    }
  }

  res.ok = true;
  for (int l = 0; l < nl; l++) {
    res.ok &= !g[l].empty();
    if (res.ml[l] != -1) {
      res.ec.emplace_back(l, res.ml[l]);
    }
  }
  for (int r = 0; r < nr; r++) {
    res.ok &= !rg[r].empty();
  }
  if (res.ok) {
    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(rg[r][0], r);
      }
    }
  } else {
    res.ec.clear();
  }
  return res;
}

/// @brief Return a minimum vertex-disjoint directed path cover of a DAG using
/// bipartite matching; edges inside each returned path are original graph
/// edges.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<int>> g(n);
  std::vector<int> deg(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  int cnt = 0;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    cnt++;
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(cnt == n);

  auto dec = bipartite_decomposition(n, n, es);
  std::vector<std::vector<int>> pth;
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    pth.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      pth.back().push_back(u);
    }
  }
  return pth;
}

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 via
/// transitive closure and Dilworth's theorem in O(n^3).
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<bool>> vis(n, std::vector<bool>(n));
  std::vector<int> deg(n);
  std::vector<std::vector<int>> g(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
    vis[v][to] = true;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  std::vector<int> top;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    top.push_back(u);
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(int(top.size()) == n);
  for (int mid : top) {
    for (int v = 0; v < n; v++) {
      if (vis[v][mid]) {
        for (int to = 0; to < n; to++) {
          vis[v][to] = vis[v][to] || vis[mid][to];
        }
      }
    }
  }
  std::vector<std::pair<int, int>> cmp;
  for (int v = 0; v < n; v++) {
    for (int to = 0; to < n; to++) {
      if (vis[v][to]) {
        cmp.emplace_back(v, to);
      }
    }
  }
  auto dec = bipartite_decomposition(n, n, cmp);
  std::vector<bool> cl(n), cr(n);
  for (int u : dec.cvl) {
    cl[u] = true;
  }
  for (int u : dec.cvr) {
    cr[u] = true;
  }
  dag_order_decomposition_result res;
  for (int u = 0; u < n; u++) {
    if (!cl[u] && !cr[u]) {
      res.ac.push_back(u);
    }
  }
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    res.cc.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      res.cc.back().push_back(u);
    }
  }
  return res;
}

} // namespace noya

#endif // NOYA_BIPARTITE_DECOMPOSITION_HPP
#include <algorithm>
#include <cassert>
#include <queue>
#include <utility>
#include <vector>

/// @complexity Time: O(E sqrt(V)) for matching/cover; DAG reductions add their constructed edges.
/// Space: O(V + E).

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; }));
  }
};

/// @brief Hopcroft-Karp matching with Konig minimum vertex cover, maximum
/// independent set, and a minimum edge cover when the graph has no isolates.
inline bipartite_decomposition_result
bipartite_decomposition(int nl, int nr,
                        const std::vector<std::pair<int, int>> &es) {
  assert(nl >= 0 && nr >= 0);
  std::vector<std::vector<int>> g(nl);
  std::vector<std::vector<int>> rg(nr);
  for (auto [l, r] : es) {
    assert(0 <= l && l < nl);
    assert(0 <= r && r < nr);
    g[l].push_back(r);
    rg[r].push_back(l);
  }
  for (auto &adj : g) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }
  for (auto &adj : rg) {
    std::sort(adj.begin(), adj.end());
    adj.erase(std::unique(adj.begin(), adj.end()), adj.end());
  }

  bipartite_decomposition_result res;
  res.ml.assign(nl, -1);
  res.mr.assign(nr, -1);
  std::vector<int> dis(nl);
  while (true) {
    std::queue<int> q;
    std::fill(dis.begin(), dis.end(), -1);
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        dis[l] = 0;
        q.push(l);
      }
    }
    while (!q.empty()) {
      int l = q.front();
      q.pop();
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt != -1 && dis[nxt] == -1) {
          dis[nxt] = dis[l] + 1;
          q.push(nxt);
        }
      }
    }
    auto aug = [&](auto &self, int l) -> bool {
      for (int r : g[l]) {
        int nxt = res.mr[r];
        if (nxt == -1 || (dis[nxt] == dis[l] + 1 && self(self, nxt))) {
          res.ml[l] = r;
          res.mr[r] = l;
          return true;
        }
      }
      dis[l] = -1;
      return false;
    };
    int au1 = 0;
    for (int l = 0; l < nl; l++) {
      if (res.ml[l] == -1) {
        au1 += aug(aug, l);
      }
    }
    if (au1 == 0) {
      break;
    }
  }

  std::vector<bool> rl(nl);
  std::vector<bool> rr(nr);
  std::queue<int> q;
  for (int l = 0; l < nl; l++) {
    if (res.ml[l] == -1) {
      rl[l] = true;
      q.push(l);
    }
  }
  while (!q.empty()) {
    int l = q.front();
    q.pop();
    for (int r : g[l]) {
      if (res.ml[l] == r || rr[r]) {
        continue;
      }
      rr[r] = true;
      int nxt = res.mr[r];
      if (nxt != -1 && !rl[nxt]) {
        rl[nxt] = true;
        q.push(nxt);
      }
    }
  }
  for (int l = 0; l < nl; l++) {
    if (rl[l]) {
      res.isl.push_back(l);
    } else {
      res.cvl.push_back(l);
    }
  }
  for (int r = 0; r < nr; r++) {
    if (rr[r]) {
      res.cvr.push_back(r);
    } else {
      res.isr.push_back(r);
    }
  }

  res.ok = true;
  for (int l = 0; l < nl; l++) {
    res.ok &= !g[l].empty();
    if (res.ml[l] != -1) {
      res.ec.emplace_back(l, res.ml[l]);
    }
  }
  for (int r = 0; r < nr; r++) {
    res.ok &= !rg[r].empty();
  }
  if (res.ok) {
    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(rg[r][0], r);
      }
    }
  } else {
    res.ec.clear();
  }
  return res;
}

/// @brief Return a minimum vertex-disjoint directed path cover of a DAG using
/// bipartite matching; edges inside each returned path are original graph
/// edges.
inline std::vector<std::vector<int>>
minimum_dag_path_cover(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<int>> g(n);
  std::vector<int> deg(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  int cnt = 0;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    cnt++;
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(cnt == n);

  auto dec = bipartite_decomposition(n, n, es);
  std::vector<std::vector<int>> pth;
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    pth.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      pth.back().push_back(u);
    }
  }
  return pth;
}

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 via
/// transitive closure and Dilworth's theorem in O(n^3).
inline dag_order_decomposition_result
dag_order_decomposition(int n, const std::vector<std::pair<int, int>> &es) {
  assert(n >= 0);
  std::vector<std::vector<bool>> vis(n, std::vector<bool>(n));
  std::vector<int> deg(n);
  std::vector<std::vector<int>> g(n);
  for (auto [v, to] : es) {
    assert(0 <= v && v < n);
    assert(0 <= to && to < n);
    g[v].push_back(to);
    deg[to]++;
    vis[v][to] = true;
  }
  std::queue<int> q;
  for (int u = 0; u < n; u++) {
    if (deg[u] == 0) {
      q.push(u);
    }
  }
  std::vector<int> top;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    top.push_back(u);
    for (int nxt : g[u]) {
      if (--deg[nxt] == 0) {
        q.push(nxt);
      }
    }
  }
  assert(int(top.size()) == n);
  for (int mid : top) {
    for (int v = 0; v < n; v++) {
      if (vis[v][mid]) {
        for (int to = 0; to < n; to++) {
          vis[v][to] = vis[v][to] || vis[mid][to];
        }
      }
    }
  }
  std::vector<std::pair<int, int>> cmp;
  for (int v = 0; v < n; v++) {
    for (int to = 0; to < n; to++) {
      if (vis[v][to]) {
        cmp.emplace_back(v, to);
      }
    }
  }
  auto dec = bipartite_decomposition(n, n, cmp);
  std::vector<bool> cl(n), cr(n);
  for (int u : dec.cvl) {
    cl[u] = true;
  }
  for (int u : dec.cvr) {
    cr[u] = true;
  }
  dag_order_decomposition_result res;
  for (int u = 0; u < n; u++) {
    if (!cl[u] && !cr[u]) {
      res.ac.push_back(u);
    }
  }
  for (int s = 0; s < n; s++) {
    if (dec.mr[s] != -1) {
      continue;
    }
    res.cc.emplace_back();
    for (int u = s; u != -1; u = dec.ml[u]) {
      res.cc.back().push_back(u);
    }
  }
  return res;
}

} // namespace noya