Skip to content

general_matching.hpp

SECTIONGraph INCLUDEnoya/general_matching.hpp

Maximum matching in a general undirected graph in O(n^3).

Verified by general_matching.

用带花树求一般无向图最大基数匹配;适合图不保证二分的配对问题。

Implementation

View on GitHub

#ifndef NOYA_GENERAL_MATCHING_HPP
#define NOYA_GENERAL_MATCHING_HPP 1

/// @complexity Time: O(V^3).
/// Space: O(V^2).

#include <algorithm>
#include <cassert>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>

namespace noya {

namespace general_matching_internal {

struct solver {
  int n;
  std::vector<std::vector<int>> graph;
  std::vector<int> match, parent, base;
  std::vector<bool> used, blossom;

  solver(int n_, const std::vector<std::pair<int, int>> &edges)
      : n(n_), graph(n), match(n, -1), parent(n), base(n), used(n), blossom(n) {
    assert(n >= 0);
    for (auto [u, v] : edges) {
      assert(0 <= u && u < n);
      assert(0 <= v && v < n);
      if (u != v) {
        graph[u].push_back(v);
        graph[v].push_back(u);
      }
    }
  }

  int least_common_ancestor(int first, int second) {
    std::vector<bool> on_path(n);
    while (true) {
      first = base[first];
      on_path[first] = true;
      if (match[first] == -1) {
        break;
      }
      first = parent[match[first]];
    }
    while (true) {
      second = base[second];
      if (on_path[second]) {
        return second;
      }
      second = parent[match[second]];
    }
  }

  void mark_path(int vertex, int common_base, int child) {
    while (base[vertex] != common_base) {
      blossom[base[vertex]] = true;
      blossom[base[match[vertex]]] = true;
      parent[vertex] = child;
      child = match[vertex];
      vertex = parent[match[vertex]];
    }
  }

  bool augment_from(int root) {
    std::fill(used.begin(), used.end(), false);
    std::fill(parent.begin(), parent.end(), -1);
    std::iota(base.begin(), base.end(), 0);

    std::queue<int> queue;
    queue.push(root);
    used[root] = true;
    while (!queue.empty()) {
      int vertex = queue.front();
      queue.pop();
      for (int to : graph[vertex]) {
        if (base[vertex] == base[to] || match[vertex] == to) {
          continue;
        }
        if (to == root || (match[to] != -1 && parent[match[to]] != -1)) {
          int common_base = least_common_ancestor(vertex, to);
          std::fill(blossom.begin(), blossom.end(), false);
          mark_path(vertex, common_base, to);
          mark_path(to, common_base, vertex);
          for (int i = 0; i < n; i++) {
            if (!blossom[base[i]]) {
              continue;
            }
            base[i] = common_base;
            if (!used[i]) {
              used[i] = true;
              queue.push(i);
            }
          }
        } else if (parent[to] == -1) {
          parent[to] = vertex;
          if (match[to] == -1) {
            for (int current = to; current != -1;) {
              int previous = parent[current];
              int next = previous == -1 ? -1 : match[previous];
              match[current] = previous;
              if (previous != -1) {
                match[previous] = current;
              }
              current = next;
            }
            return true;
          }
          to = match[to];
          used[to] = true;
          queue.push(to);
        }
      }
    }
    return false;
  }

  std::pair<int, std::vector<int>> run() {
    int size = 0;
    for (int vertex = 0; vertex < n; vertex++) {
      if (match[vertex] == -1 && augment_from(vertex)) {
        size++;
      }
    }
    return {size, match};
  }
};

} // namespace general_matching_internal

/// @brief Maximum matching in a general undirected graph in O(n^3).
/// @return (matching size, mate of every vertex), with -1 for unmatched
/// vertices.
inline std::pair<int, std::vector<int>>
maximum_matching(int n, const std::vector<std::pair<int, int>> &edges) {
  return general_matching_internal::solver(n, edges).run();
}

} // namespace noya

#endif // NOYA_GENERAL_MATCHING_HPP
#include <algorithm>
#include <cassert>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>

/// @complexity Time: O(V^3).
/// Space: O(V^2).

namespace noya {

namespace general_matching_internal {

struct solver {
  int n;
  std::vector<std::vector<int>> graph;
  std::vector<int> match, parent, base;
  std::vector<bool> used, blossom;

  solver(int n_, const std::vector<std::pair<int, int>> &edges)
      : n(n_), graph(n), match(n, -1), parent(n), base(n), used(n), blossom(n) {
    assert(n >= 0);
    for (auto [u, v] : edges) {
      assert(0 <= u && u < n);
      assert(0 <= v && v < n);
      if (u != v) {
        graph[u].push_back(v);
        graph[v].push_back(u);
      }
    }
  }

  int least_common_ancestor(int first, int second) {
    std::vector<bool> on_path(n);
    while (true) {
      first = base[first];
      on_path[first] = true;
      if (match[first] == -1) {
        break;
      }
      first = parent[match[first]];
    }
    while (true) {
      second = base[second];
      if (on_path[second]) {
        return second;
      }
      second = parent[match[second]];
    }
  }

  void mark_path(int vertex, int common_base, int child) {
    while (base[vertex] != common_base) {
      blossom[base[vertex]] = true;
      blossom[base[match[vertex]]] = true;
      parent[vertex] = child;
      child = match[vertex];
      vertex = parent[match[vertex]];
    }
  }

  bool augment_from(int root) {
    std::fill(used.begin(), used.end(), false);
    std::fill(parent.begin(), parent.end(), -1);
    std::iota(base.begin(), base.end(), 0);

    std::queue<int> queue;
    queue.push(root);
    used[root] = true;
    while (!queue.empty()) {
      int vertex = queue.front();
      queue.pop();
      for (int to : graph[vertex]) {
        if (base[vertex] == base[to] || match[vertex] == to) {
          continue;
        }
        if (to == root || (match[to] != -1 && parent[match[to]] != -1)) {
          int common_base = least_common_ancestor(vertex, to);
          std::fill(blossom.begin(), blossom.end(), false);
          mark_path(vertex, common_base, to);
          mark_path(to, common_base, vertex);
          for (int i = 0; i < n; i++) {
            if (!blossom[base[i]]) {
              continue;
            }
            base[i] = common_base;
            if (!used[i]) {
              used[i] = true;
              queue.push(i);
            }
          }
        } else if (parent[to] == -1) {
          parent[to] = vertex;
          if (match[to] == -1) {
            for (int current = to; current != -1;) {
              int previous = parent[current];
              int next = previous == -1 ? -1 : match[previous];
              match[current] = previous;
              if (previous != -1) {
                match[previous] = current;
              }
              current = next;
            }
            return true;
          }
          to = match[to];
          used[to] = true;
          queue.push(to);
        }
      }
    }
    return false;
  }

  std::pair<int, std::vector<int>> run() {
    int size = 0;
    for (int vertex = 0; vertex < n; vertex++) {
      if (match[vertex] == -1 && augment_from(vertex)) {
        size++;
      }
    }
    return {size, match};
  }
};

} // namespace general_matching_internal

/// @brief Maximum matching in a general undirected graph in O(n^3).
/// @return (matching size, mate of every vertex), with -1 for unmatched
/// vertices.
inline std::pair<int, std::vector<int>>
maximum_matching(int n, const std::vector<std::pair<int, int>> &edges) {
  return general_matching_internal::solver(n, edges).run();
}

} // namespace noya