pub const FORBIDDEN: i64 = 1 << 50;
pub fn min_weight_perfect(n: usize, cost: &[i64]) -> Option<(Vec<usize>, i64)> {
if n == 0 {
return Some((Vec::new(), 0));
}
if n % 2 == 1 || cost.len() != n * n {
return None;
}
let mut hi = i64::MIN;
for i in 0..n {
for j in (i + 1)..n {
if cost[i * n + j] < FORBIDDEN {
hi = hi.max(cost[i * n + j]);
}
}
}
if hi == i64::MIN {
return None; }
let mut w = vec![0i64; (n + 1) * (n + 1)];
for i in 0..n {
for j in 0..n {
if i != j && cost[i * n + j] < FORBIDDEN {
w[(i + 1) * (n + 1) + (j + 1)] = hi + 1 - cost[i * n + j];
}
}
}
let mate = Blossom::new(n, w).solve()?;
let mut total = 0i64;
for i in 0..n {
if mate[i] == i {
return None;
}
if mate[i] > i {
total += cost[i * n + mate[i]];
}
}
Some((mate, total))
}
struct Blossom {
n: usize,
n_x: usize,
g: Vec<Vec<(usize, usize, i64)>>,
lab: Vec<i64>,
match_: Vec<usize>,
st: Vec<usize>,
pa: Vec<usize>,
s: Vec<u8>,
slack: Vec<usize>,
flower: Vec<Vec<usize>>,
flower_from: Vec<Vec<usize>>,
q: Vec<usize>,
}
const OUTER: u8 = 0;
const INNER: u8 = 1;
const UNLABELLED: u8 = 2;
impl Blossom {
fn new(n: usize, w: Vec<i64>) -> Blossom {
let m = n * 2 + 1;
let mut g = vec![vec![(0usize, 0usize, 0i64); m]; m];
for u in 1..=n {
for v in 1..=n {
g[u][v] = (u, v, w[u * (n + 1) + v]);
}
}
Blossom {
n,
n_x: n,
g,
lab: vec![0; m],
match_: vec![0; m],
st: vec![0; m],
pa: vec![0; m],
s: vec![UNLABELLED; m],
slack: vec![0; m],
flower: vec![Vec::new(); m],
flower_from: vec![vec![0; n + 1]; m],
q: Vec::new(),
}
}
#[inline]
fn e_delta(&self, e: (usize, usize, i64)) -> i64 {
self.lab[e.0] + self.lab[e.1] - e.2 * 2
}
fn update_slack(&mut self, u: usize, x: usize) {
if self.slack[x] == 0 || self.e_delta(self.g[u][x]) < self.e_delta(self.g[self.slack[x]][x])
{
self.slack[x] = u;
}
}
fn set_slack(&mut self, x: usize) {
self.slack[x] = 0;
for u in 1..=self.n {
if self.g[u][x].2 > 0 && self.st[u] != x && self.s[self.st[u]] == OUTER {
self.update_slack(u, x);
}
}
}
fn q_push(&mut self, x: usize) {
if x <= self.n {
self.q.push(x);
} else {
for i in 0..self.flower[x].len() {
let f = self.flower[x][i];
self.q_push(f);
}
}
}
fn set_st(&mut self, x: usize, b: usize) {
self.st[x] = b;
if x > self.n {
for i in 0..self.flower[x].len() {
let f = self.flower[x][i];
self.set_st(f, b);
}
}
}
fn get_pr(&mut self, b: usize, xr: usize) -> usize {
let pr = self.flower[b].iter().position(|&v| v == xr).expect("xr is a petal of b");
if pr % 2 == 1 {
let k = self.flower[b].len();
self.flower[b][1..].reverse();
k - pr
} else {
pr
}
}
fn set_match(&mut self, u: usize, v: usize) {
let e = self.g[u][v];
self.match_[u] = e.1;
if u <= self.n {
return;
}
let xr = self.flower_from[u][e.0];
let pr = self.get_pr(u, xr);
for i in 0..pr {
let a = self.flower[u][i];
let b = self.flower[u][i ^ 1];
self.set_match(a, b);
}
self.set_match(xr, v);
self.flower[u].rotate_left(pr);
}
fn augment(&mut self, mut u: usize, mut v: usize) {
loop {
let xnv = self.st[self.match_[u]];
self.set_match(u, v);
if xnv == 0 {
return;
}
let t = self.st[self.pa[xnv]];
self.set_match(xnv, t);
u = t;
v = xnv;
}
}
fn lca(&mut self, mut u: usize, mut v: usize) -> usize {
let mut seen = vec![false; self.n_x + 1];
loop {
while u != 0 {
if seen[u] {
return u;
}
seen[u] = true;
u = self.st[self.match_[u]];
if u != 0 {
u = self.st[self.pa[u]];
}
core::mem::swap(&mut u, &mut v);
}
if v == 0 {
return 0;
}
core::mem::swap(&mut u, &mut v);
}
}
fn add_blossom(&mut self, u: usize, lca: usize, v: usize) {
let mut b = self.n + 1;
while b <= self.n_x && self.st[b] != 0 {
b += 1;
}
if b > self.n_x {
self.n_x += 1;
}
self.lab[b] = 0;
self.s[b] = OUTER;
self.match_[b] = self.match_[lca];
self.flower[b].clear();
self.flower[b].push(lca);
let mut x = u;
while x != lca {
let y = self.st[self.match_[x]];
self.flower[b].push(x);
self.flower[b].push(y);
self.q_push(y);
x = self.st[self.pa[y]];
}
self.flower[b][1..].reverse();
let mut x = v;
while x != lca {
let y = self.st[self.match_[x]];
self.flower[b].push(x);
self.flower[b].push(y);
self.q_push(y);
x = self.st[self.pa[y]];
}
self.set_st(b, b);
for x in 1..=self.n_x {
self.g[b][x].2 = 0;
self.g[x][b].2 = 0;
}
for x in 1..=self.n {
self.flower_from[b][x] = 0;
}
let petals = self.flower[b].clone();
for &xs in &petals {
for x in 1..=self.n_x {
if self.g[b][x].2 == 0 || self.e_delta(self.g[xs][x]) < self.e_delta(self.g[b][x]) {
self.g[b][x] = self.g[xs][x];
self.g[x][b] = self.g[x][xs];
}
}
for x in 1..=self.n {
if self.flower_from[xs][x] != 0 {
self.flower_from[b][x] = xs;
}
}
}
self.set_slack(b);
}
fn expand_blossom(&mut self, b: usize) {
let petals = self.flower[b].clone();
for &f in &petals {
self.set_st(f, f);
}
let xr = self.flower_from[b][self.g[b][self.pa[b]].0];
let pr = self.get_pr(b, xr);
let mut i = 0;
while i < pr {
let xs = self.flower[b][i];
let xns = self.flower[b][i + 1];
self.pa[xs] = self.g[xns][xs].0;
self.s[xs] = INNER;
self.s[xns] = OUTER;
self.slack[xs] = 0;
self.set_slack(xns);
self.q_push(xns);
i += 2;
}
self.s[xr] = INNER;
self.pa[xr] = self.pa[b];
for i in (pr + 1)..self.flower[b].len() {
let xs = self.flower[b][i];
self.s[xs] = UNLABELLED;
self.set_slack(xs);
}
self.st[b] = 0;
}
fn on_found_edge(&mut self, e: (usize, usize, i64)) -> bool {
let (xu, xv) = (self.st[e.0], self.st[e.1]);
if self.s[xv] == UNLABELLED {
self.pa[xv] = e.0;
self.s[xv] = INNER;
let nu = self.st[self.match_[xv]];
self.slack[xv] = 0;
self.slack[nu] = 0;
self.s[nu] = OUTER;
self.q_push(nu);
} else if self.s[xv] == OUTER {
let lca = self.lca(xu, xv);
if lca == 0 {
self.augment(xu, xv);
self.augment(xv, xu);
return true;
}
self.add_blossom(xu, lca, xv);
}
false
}
fn matching(&mut self) -> bool {
for x in 0..=self.n_x {
self.s[x] = UNLABELLED;
self.slack[x] = 0;
}
self.q.clear();
for x in 1..=self.n_x {
if self.st[x] == x && self.match_[x] == 0 {
self.pa[x] = 0;
self.s[x] = OUTER;
self.q_push(x);
}
}
if self.q.is_empty() {
return false;
}
loop {
while let Some(u) = self.q.pop() {
if self.s[self.st[u]] == INNER {
continue;
}
for v in 1..=self.n {
if self.g[u][v].2 > 0 && self.st[u] != self.st[v] {
if self.e_delta(self.g[u][v]) == 0 {
if self.on_found_edge(self.g[u][v]) {
return true;
}
} else {
let x = self.st[v];
self.update_slack(u, x);
}
}
}
}
let mut d = i64::MAX;
for b in (self.n + 1)..=self.n_x {
if self.st[b] == b && self.s[b] == INNER {
d = d.min(self.lab[b] / 2);
}
}
for x in 1..=self.n_x {
if self.st[x] == x && self.slack[x] != 0 {
let sl = self.e_delta(self.g[self.slack[x]][x]);
match self.s[x] {
UNLABELLED => d = d.min(sl),
OUTER => d = d.min(sl / 2),
_ => {}
}
}
}
for u in 1..=self.n {
match self.s[self.st[u]] {
OUTER => {
if self.lab[u] <= d {
return false;
}
self.lab[u] -= d;
}
INNER => self.lab[u] += d,
_ => {}
}
}
for b in (self.n + 1)..=self.n_x {
if self.st[b] == b {
match self.s[b] {
OUTER => self.lab[b] += d * 2,
INNER => self.lab[b] -= d * 2,
_ => {}
}
}
}
self.q.clear();
for x in 1..=self.n_x {
if self.st[x] == x
&& self.slack[x] != 0
&& self.st[self.slack[x]] != x
&& self.e_delta(self.g[self.slack[x]][x]) == 0
&& self.on_found_edge(self.g[self.slack[x]][x])
{
return true;
}
}
for b in (self.n + 1)..=self.n_x {
if self.st[b] == b && self.s[b] == INNER && self.lab[b] == 0 {
self.expand_blossom(b);
}
}
}
}
fn solve(mut self) -> Option<Vec<usize>> {
let n = self.n;
for u in 0..=n {
self.match_[u] = 0;
self.st[u] = u;
}
self.n_x = n;
let mut w_max = 0i64;
for u in 1..=n {
for v in 1..=n {
self.flower_from[u][v] = if u == v { u } else { 0 };
w_max = w_max.max(self.g[u][v].2);
}
}
for u in 1..=n {
self.lab[u] = w_max;
}
for _ in 0..(n / 2) {
if !self.matching() {
return None;
}
}
let mut mate = vec![usize::MAX; n];
for u in 1..=n {
let m = self.match_[u];
if m == 0 || m > n {
return None;
}
mate[u - 1] = m - 1;
}
for u in 0..n {
if mate[mate[u]] != u {
return None; }
}
Some(mate)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rng::Pcg;
fn brute(n: usize, cost: &[i64]) -> Option<i64> {
fn go(n: usize, cost: &[i64], used: &mut Vec<bool>, k: usize, acc: i64, best: &mut i64) {
if k == n {
*best = (*best).min(acc);
return;
}
if used[k] {
go(n, cost, used, k + 1, acc, best);
return;
}
used[k] = true;
for j in (k + 1)..n {
if !used[j] && cost[k * n + j] < FORBIDDEN {
used[j] = true;
go(n, cost, used, k + 1, acc + cost[k * n + j], best);
used[j] = false;
}
}
used[k] = false;
}
if n % 2 == 1 {
return None;
}
let mut best = i64::MAX;
go(n, cost, &mut vec![false; n], 0, 0, &mut best);
(best != i64::MAX).then_some(best)
}
fn random_cost(n: usize, seed: u64, hi: i64) -> Vec<i64> {
let mut rng = Pcg::new(seed, 0x0B10_5503);
let mut c = vec![0i64; n * n];
for i in 0..n {
for j in (i + 1)..n {
let v = (rng.next_u32() as i64) % hi;
c[i * n + j] = v;
c[j * n + i] = v;
}
}
c
}
#[test]
fn it_agrees_with_exhaustive_enumeration() {
for n in [2usize, 4, 6, 8, 10] {
for seed in 0..40u64 {
let c = random_cost(n, seed + n as u64 * 1000, 100);
let (mate, total) = min_weight_perfect(n, &c).expect("complete graph, even order");
let truth = brute(n, &c).expect("complete graph, even order");
assert_eq!(
total, truth,
"n={n} seed={seed}: blossom {total}, exhaustive {truth}"
);
let mut sum = 0;
for i in 0..n {
assert_eq!(mate[mate[i]], i, "not an involution at {i}");
assert_ne!(mate[i], i);
if mate[i] > i {
sum += c[i * n + mate[i]];
}
}
assert_eq!(sum, total, "the reported total is not the matching's weight");
}
}
}
#[test]
fn an_odd_cycle_does_not_defeat_it() {
let n = 6;
let mut c = vec![50i64; n * n];
for i in 0..n {
c[i * n + i] = 0;
}
let set = |a: usize, b: usize, v: i64, c: &mut Vec<i64>| {
c[a * n + b] = v;
c[b * n + a] = v;
};
for (a, b) in [(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3)] {
set(a, b, 10, &mut c);
}
set(2, 3, 1, &mut c);
let (_, total) = min_weight_perfect(n, &c).unwrap();
assert_eq!(total, brute(n, &c).unwrap());
assert_eq!(total, 21, "bridge 1 + one edge from each triangle 10 + 10");
}
#[test]
fn a_missing_perfect_matching_is_reported_rather_than_invented() {
let n = 4;
let mut c = vec![FORBIDDEN; n * n];
for i in 0..n {
c[i * n + i] = 0;
}
c[1] = 1;
c[n] = 1;
assert!(min_weight_perfect(n, &c).is_none(), "there is no perfect matching here");
c[2 * n + 3] = 5;
c[3 * n + 2] = 5;
let (_, total) = min_weight_perfect(n, &c).unwrap();
assert_eq!(total, 6);
}
#[test]
fn odd_order_and_empty_are_answered_not_attempted() {
assert!(min_weight_perfect(3, &[0i64; 9]).is_none(), "odd order has no perfect matching");
assert_eq!(min_weight_perfect(0, &[]), Some((Vec::new(), 0)));
assert!(min_weight_perfect(4, &[0i64; 3]).is_none(), "a cost matrix of the wrong shape");
}
#[test]
fn a_planted_optimum_is_found_at_sizes_brute_force_cannot_reach() {
for n in [50usize, 200, 500] {
let mut rng = Pcg::new(n as u64, 0x091A_47ED);
let mut perm: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = (rng.next_u32() as usize) % (i + 1);
perm.swap(i, j);
}
let mut c = vec![1i64; n * n];
for i in 0..n {
c[i * n + i] = 0;
}
for k in (0..n).step_by(2) {
let (a, b) = (perm[k], perm[k + 1]);
c[a * n + b] = 0;
c[b * n + a] = 0;
}
let (mate, total) = min_weight_perfect(n, &c).expect("a perfect matching is planted");
assert_eq!(total, 0, "n={n}: the planted matching costs 0 and this found {total}");
for i in 0..n {
assert_eq!(mate[mate[i]], i);
assert_eq!(c[i * n + mate[i]], 0, "n={n}: vertex {i} matched off the plant");
}
}
}
#[test]
fn negative_costs_are_handled_rather_than_assumed_away() {
for n in [4usize, 6, 8] {
for seed in 0..20u64 {
let mut c = random_cost(n, seed + 77, 40);
for v in c.iter_mut() {
*v -= 20;
}
for i in 0..n {
c[i * n + i] = 0;
}
let (_, total) = min_weight_perfect(n, &c).unwrap();
assert_eq!(total, brute(n, &c).unwrap(), "n={n} seed={seed}");
}
}
}
}