競技プログラミング用 C++ ライブラリ
graph/general_weighted_matching.hpp · GitHub で見る
共通ページ
#include "lib/graph/general_weighted_matching.hpp"
GeneralWeightedMatching graph(4);
graph.add_edge(0, 1, 1);
graph.add_edge(1, 2, 3);
graph.add_edge(2, 3, 1);
auto total = graph.build(); // 3
auto count = graph.matching_size(); // 1
auto pairs = graph.edges(); // { {1,2} }
auto partner = graph.mate[1]; // 2
無向一般グラフで、重みの合計が最大になるマッチングを求めます。重み付き Blossom 法を使い、奇数長の閉路も扱えます。頂点は 0..n-1 です。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
GeneralWeightedMatching(n = 0) |
n 頂点の空グラフを作る |
\(O(n^2)\) |
size() |
頂点数 | \(O(1)\) |
add_edge(u,v,w) |
重み w の無向辺を追加。同じ頂点対では最大の重みを使う |
\(O(1)\) |
build() |
最大重みマッチングを計算し、重みの合計を total_type で返す。変更がなければ前回の結果を返す |
変更後: \(O(n^3)\)、変更なし: \(O(1)\) |
weight() |
計算済みの重みの合計 | \(O(1)\) |
matching_size() |
計算済みのマッチングの組数 | \(O(1)\) |
edges() |
マッチングの頂点対を vector<pair<int,int>> で返す。各組は u < v
|
\(O(n)\) |
mate[v] |
相手の頂点。マッチしていなければ -1。参照用 | \(O(1)\) |
clear() |
頂点数・確保容量を保ち、全辺と計算結果を消す | \(O(n^2)\) |
swap(other) |
頂点数・辺・計算結果を交換 | \(O(1)\) |
weight_type |
辺重みの型。int64_t
|
—(型) |
total_type |
重みの合計の型。__int128_t
|
—(型) |
最大化するのは重みの合計です。組数の最大化や、完全マッチングを要求する操作ではありません。同じ最大重みを持つ解が複数ある場合、どの解を返すかは未規定です。上の例では重み 3 の 1 組を選び、重み 1 の 2 組は選びません。
自己ループと重み 0 以下の辺は無視します。負の重みや INT64_MIN も渡せます。辺重みは int64_t の全範囲で使え、合計が 64 bit を超えても正しく計算します。内部の整数型は頂点数と最大重みに応じて 32/64/128 bit から選びます。
辺の追加後に再び build() できます。有効な辺を追加した後は、再計算するまで weight()・matching_size()・edges()・mate を使わないでください。構築直後・clear() 直後は空の結果を参照できます。mate は書き換えないでください。
コピー・ムーブに対応し、移動元は 0 頂点の空グラフになります。n は非負で 2*n+1 が int に収まる値としてください。重みは整数だけに対応します。
頂点数を \(n\) とします。構築・clear は \(O(n^2)\)、辺の追加・size・weight・matching_size・swap は \(O(1)\)、build は \(O(n^3)\) 時間です。グラフが変化していない場合の build は \(O(1)\)、edges は \(O(n)\) 時間です。保持空間・計算中の追加空間はともに \(O(n^2)\)、edges の返り値は \(O(n)\) 空間です。\(n=0\) の操作は \(O(1)\) です。
#ifndef GENERAL_WEIGHTED_MATCHING_HPP
#define GENERAL_WEIGHTED_MATCHING_HPP
#include "../components/weighted_blossom.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
class GeneralWeightedMatching {
int n;
std::vector<std::int64_t> weights;
std::int64_t maximum = 0;
__int128_t total = 0;
int cardinality = 0;
bool built = true;
static int checked_size(int n) { assert(0 <= n && n <= (std::numeric_limits<int>::max() - 1) / 2); return n; }
void vertex([[maybe_unused]] int v) const { assert(0 <= v && v < n); }
public:
using weight_type = std::int64_t;
using total_type = __int128_t;
std::vector<int> mate;
explicit GeneralWeightedMatching(int n = 0) : n(checked_size(n)), weights(std::size_t(this->n) * this->n), mate(this->n, -1) {}
GeneralWeightedMatching(const GeneralWeightedMatching &) = default;
GeneralWeightedMatching &operator=(const GeneralWeightedMatching &) = default;
GeneralWeightedMatching(GeneralWeightedMatching &&other) noexcept : GeneralWeightedMatching() { swap(other); }
GeneralWeightedMatching &operator=(GeneralWeightedMatching &&other) noexcept {
if (this != &other) { GeneralWeightedMatching moved(std::move(other)); swap(moved); }
return *this;
}
int size() const { return n; }
void add_edge(int u, int v, weight_type weight) {
vertex(u); vertex(v);
if (u == v || weight <= 0) return;
auto ¤t = weights[std::size_t(u) * n + v];
if (weight <= current) return;
current = weights[std::size_t(v) * n + u] = weight;
maximum = std::max(maximum, weight); built = false;
}
total_type build() {
if (built) return total;
std::int64_t bound = 4 * (std::int64_t(n) + 1);
if (maximum <= std::numeric_limits<int>::max() / bound)
weighted_matching_detail::Blossom<int>(n, weights, int(maximum)).solve(mate);
else if (maximum <= std::numeric_limits<std::int64_t>::max() / bound)
weighted_matching_detail::Blossom<std::int64_t>(n, weights, maximum).solve(mate);
else weighted_matching_detail::Blossom<__int128_t>(n, weights, maximum).solve(mate);
total = 0; cardinality = 0;
for (int u = 0; u < n; ++u) if (u < mate[u]) {
total += weights[std::size_t(u) * n + mate[u]]; ++cardinality;
}
built = true; return total;
}
total_type weight() const { assert(built); return total; }
int matching_size() const { assert(built); return cardinality; }
std::vector<std::pair<int, int>> edges() const {
assert(built);
std::vector<std::pair<int, int>> result; result.reserve(cardinality);
for (int u = 0; u < n; ++u) if (u < mate[u]) result.emplace_back(u, mate[u]);
return result;
}
void clear() {
std::fill(weights.begin(), weights.end(), 0); std::fill(mate.begin(), mate.end(), -1);
maximum = 0; total = 0; cardinality = 0; built = true;
}
void swap(GeneralWeightedMatching &other) noexcept {
using std::swap;
swap(n, other.n); weights.swap(other.weights); swap(maximum, other.maximum);
swap(total, other.total); swap(cardinality, other.cardinality); swap(built, other.built); mate.swap(other.mate);
}
};
#endif
#line 1 "graph/general_weighted_matching.hpp"
#line 1 "components/weighted_blossom.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <span>
#include <utility>
#include <vector>
namespace weighted_matching_detail {
template <class T> class Blossom {
struct Edge { int u = 0, v = 0; T weight = 0; };
int n, stride, nodes;
std::size_t head = 0;
std::uint32_t stamp = 0;
std::vector<Edge> graph;
std::vector<T> dual;
std::vector<int> match, base, parent, slack, from, queue, adjacent;
std::vector<std::size_t> start;
std::vector<signed char> state;
std::vector<std::uint32_t> seen;
std::vector<std::vector<int>> children;
Edge &edge(int u, int v) { return graph[std::size_t(u) * stride + v]; }
int &child(int b, int v) { return from[std::size_t(b) * (n + 1) + v]; }
T gap(const Edge &e) const { return dual[e.u] + dual[e.v] - e.weight * 2; }
void relax(int u, int b) {
if (!slack[b] || gap(edge(u, b)) < gap(edge(slack[b], b))) slack[b] = u;
}
void reset_slack(int b) {
slack[b] = 0;
for (int u = 1; u <= n; ++u)
if (base[u] != b && state[base[u]] == 0 && edge(u, b).weight > 0) relax(u, b);
}
void enqueue(int b) {
if (b <= n) queue.push_back(b);
else for (int c : children[b]) enqueue(c);
}
void set_base(int b, int root) {
base[b] = root;
if (b > n) for (int c : children[b]) set_base(c, root);
}
int orient(int b, int c) {
auto &list = children[b];
int p = std::find(list.begin(), list.end(), c) - list.begin();
assert(p < int(list.size()));
if (p & 1) { std::reverse(list.begin() + 1, list.end()); p = int(list.size()) - p; }
return p;
}
void set_match(int b, int v) {
Edge e = edge(b, v);
match[b] = e.v;
if (b <= n) return;
int c = child(b, e.u), p = orient(b, c);
auto &list = children[b];
for (int i = 0; i < p; ++i) set_match(list[i], list[i ^ 1]);
set_match(c, v);
std::rotate(list.begin(), list.begin() + p, list.end());
}
void augment(int u, int v) {
while (u) {
int next = base[match[u]];
set_match(u, v);
if (!next) break;
u = base[parent[next]];
set_match(next, u);
v = next;
}
}
int lca(int u, int v) {
if (!++stamp) std::fill(seen.begin(), seen.end(), 0), ++stamp;
for (; u || v; std::swap(u, v)) if (u) {
if (seen[u] == stamp) return u;
seen[u] = stamp;
u = base[match[u]];
if (u) u = base[parent[u]];
}
return 0;
}
void contract(int u, int v, int root) {
int b = n + 1;
while (b <= nodes && base[b]) ++b;
nodes = std::max(nodes, b);
dual[b] = 0; state[b] = 0; match[b] = match[root];
auto &list = children[b];
list.clear(); list.push_back(root);
for (int x = u; x != root;) {
int y = base[match[x]];
list.push_back(x); list.push_back(y); enqueue(y);
x = base[parent[y]];
}
std::reverse(list.begin() + 1, list.end());
for (int x = v; x != root;) {
int y = base[match[x]];
list.push_back(x); list.push_back(y); enqueue(y);
x = base[parent[y]];
}
set_base(b, b);
for (int x = 1; x <= nodes; ++x) edge(b, x) = edge(x, b) = Edge{};
std::fill_n(from.begin() + std::size_t(b) * (n + 1), n + 1, 0);
for (int c : list) {
for (int x = 1; x <= nodes; ++x) if (edge(c, x).weight > 0) {
if (!edge(b, x).weight || gap(edge(c, x)) < gap(edge(b, x))) {
edge(b, x) = edge(c, x); edge(x, b) = edge(x, c);
}
}
for (int x = 1; x <= n; ++x) if (child(c, x)) child(b, x) = c;
}
reset_slack(b);
}
void expand(int b) {
for (int c : children[b]) set_base(c, c);
int entry = child(b, edge(b, parent[b]).u), p = orient(b, entry);
auto &list = children[b];
for (int i = 0; i < p; i += 2) {
int u = list[i], v = list[i + 1];
parent[u] = edge(v, u).u;
state[u] = 1; state[v] = 0; slack[u] = 0;
reset_slack(v); enqueue(v);
}
state[entry] = 1; parent[entry] = parent[b]; slack[entry] = 0;
for (int i = p + 1; i < int(list.size()); ++i) {
state[list[i]] = -1; reset_slack(list[i]);
}
base[b] = 0;
}
bool found(const Edge &e) {
int u = base[e.u], v = base[e.v];
if (state[v] == -1) {
parent[v] = e.u; state[v] = 1;
int next = base[match[v]];
assert(next);
slack[v] = slack[next] = 0; state[next] = 0; enqueue(next);
} else if (state[v] == 0) {
int root = lca(u, v);
if (!root) { augment(u, v); augment(v, u); return true; }
contract(u, v, root);
}
return false;
}
bool step() {
std::fill(state.begin(), state.end(), -1);
std::fill(slack.begin(), slack.end(), 0);
queue.clear(); head = 0;
for (int b = 1; b <= nodes; ++b) if (base[b] == b && !match[b]) {
parent[b] = 0; state[b] = 0; enqueue(b);
}
if (queue.empty()) return false;
for (;;) {
while (head < queue.size()) {
int u = queue[head++];
if (state[base[u]] != 0) continue;
for (std::size_t i = start[u]; i < start[u + 1]; ++i) {
int v = adjacent[i];
if (base[u] == base[v]) continue;
if (!gap(edge(u, v))) { if (found(edge(u, v))) return true; }
else relax(u, base[v]);
}
}
T delta = std::numeric_limits<T>::max() / 2;
for (int b = n + 1; b <= nodes; ++b)
if (base[b] == b && state[b] == 1) delta = std::min(delta, dual[b] / 2);
for (int b = 1; b <= nodes; ++b) if (base[b] == b && slack[b]) {
T d = gap(edge(slack[b], b));
if (state[b] == -1) delta = std::min(delta, d);
else if (state[b] == 0) delta = std::min(delta, d / 2);
}
for (int u = 1; u <= n; ++u) {
if (state[base[u]] == 0) {
if (dual[u] <= delta) return false;
dual[u] -= delta;
} else if (state[base[u]] == 1) dual[u] += delta;
}
for (int b = n + 1; b <= nodes; ++b) if (base[b] == b) {
if (state[b] == 0) dual[b] += delta * 2;
else if (state[b] == 1) dual[b] -= delta * 2;
}
queue.clear(); head = 0;
for (int b = 1; b <= nodes; ++b)
if (base[b] == b && slack[b] && base[slack[b]] != b && !gap(edge(slack[b], b)))
if (found(edge(slack[b], b))) return true;
for (int b = n + 1; b <= nodes; ++b)
if (base[b] == b && state[b] == 1 && !dual[b]) expand(b);
}
}
public:
Blossom(int n, std::span<const std::int64_t> weights, T maximum)
: n(n), stride(2 * n + 1), nodes(n), graph(std::size_t(stride) * stride), dual(stride),
match(stride), base(stride), parent(stride), slack(stride), from(std::size_t(stride) * (n + 1)),
start(n + 2), state(stride, -1), seen(stride), children(stride) {
for (int u = 1; u <= n; ++u) {
base[u] = u; child(u, u) = u; dual[u] = maximum;
start[u] = adjacent.size();
for (int v = 1; v <= n; ++v) {
T weight = T(weights[std::size_t(u - 1) * n + v - 1]);
edge(u, v) = Edge{u, v, weight};
if (weight > 0) adjacent.push_back(v);
}
}
start[n + 1] = adjacent.size();
queue.reserve(n);
}
void solve(std::vector<int> &mate) {
while (step()) {}
for (int u = 1; u <= n; ++u) mate[u - 1] = match[u] ? match[u] - 1 : -1;
}
};
}
#line 12 "graph/general_weighted_matching.hpp"
class GeneralWeightedMatching {
int n;
std::vector<std::int64_t> weights;
std::int64_t maximum = 0;
__int128_t total = 0;
int cardinality = 0;
bool built = true;
static int checked_size(int n) { assert(0 <= n && n <= (std::numeric_limits<int>::max() - 1) / 2); return n; }
void vertex([[maybe_unused]] int v) const { assert(0 <= v && v < n); }
public:
using weight_type = std::int64_t;
using total_type = __int128_t;
std::vector<int> mate;
explicit GeneralWeightedMatching(int n = 0) : n(checked_size(n)), weights(std::size_t(this->n) * this->n), mate(this->n, -1) {}
GeneralWeightedMatching(const GeneralWeightedMatching &) = default;
GeneralWeightedMatching &operator=(const GeneralWeightedMatching &) = default;
GeneralWeightedMatching(GeneralWeightedMatching &&other) noexcept : GeneralWeightedMatching() { swap(other); }
GeneralWeightedMatching &operator=(GeneralWeightedMatching &&other) noexcept {
if (this != &other) { GeneralWeightedMatching moved(std::move(other)); swap(moved); }
return *this;
}
int size() const { return n; }
void add_edge(int u, int v, weight_type weight) {
vertex(u); vertex(v);
if (u == v || weight <= 0) return;
auto ¤t = weights[std::size_t(u) * n + v];
if (weight <= current) return;
current = weights[std::size_t(v) * n + u] = weight;
maximum = std::max(maximum, weight); built = false;
}
total_type build() {
if (built) return total;
std::int64_t bound = 4 * (std::int64_t(n) + 1);
if (maximum <= std::numeric_limits<int>::max() / bound)
weighted_matching_detail::Blossom<int>(n, weights, int(maximum)).solve(mate);
else if (maximum <= std::numeric_limits<std::int64_t>::max() / bound)
weighted_matching_detail::Blossom<std::int64_t>(n, weights, maximum).solve(mate);
else weighted_matching_detail::Blossom<__int128_t>(n, weights, maximum).solve(mate);
total = 0; cardinality = 0;
for (int u = 0; u < n; ++u) if (u < mate[u]) {
total += weights[std::size_t(u) * n + mate[u]]; ++cardinality;
}
built = true; return total;
}
total_type weight() const { assert(built); return total; }
int matching_size() const { assert(built); return cardinality; }
std::vector<std::pair<int, int>> edges() const {
assert(built);
std::vector<std::pair<int, int>> result; result.reserve(cardinality);
for (int u = 0; u < n; ++u) if (u < mate[u]) result.emplace_back(u, mate[u]);
return result;
}
void clear() {
std::fill(weights.begin(), weights.end(), 0); std::fill(mate.begin(), mate.end(), -1);
maximum = 0; total = 0; cardinality = 0; built = true;
}
void swap(GeneralWeightedMatching &other) noexcept {
using std::swap;
swap(n, other.n); weights.swap(other.weights); swap(maximum, other.maximum);
swap(total, other.total); swap(cardinality, other.cardinality); swap(built, other.built); mate.swap(other.mate);
}
};