Skip to content

Commit 00cc846

Browse files
authored
Bipartite matching (#65)
* adding progress * finish yosupo test * nit * finish docs * fix code cov * another rename * move duplicate code to new file * I like this way more * add back other way as aizu test doesn't check actual matching * fix one nit * address another comment * add comment * remove duplicate code * convert * fix documentation * finish docs --------- Co-authored-by: Luke Videckis <lukevideckis@gmail.com>
1 parent 93deb9b commit 00cc846

File tree

6 files changed

+223
-0
lines changed

6 files changed

+223
-0
lines changed

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ path = "examples/graphs/hld_jump_on_tree_nodes.rs"
127127
name = "hld_jump_on_tree_edges"
128128
path = "examples/graphs/hld_jump_on_tree_edges.rs"
129129

130+
[[example]]
131+
name = "hopcroft_karp_yosupo"
132+
path = "examples/graphs/hopcroft_karp_yosupo.rs"
133+
134+
[[example]]
135+
name = "hopcroft_karp_aizu"
136+
path = "examples/graphs/hopcroft_karp_aizu.rs"
137+
130138
[[example]]
131139
name = "lcp_query_yosupo_zfunc"
132140
path = "examples/strings/lcp_query_yosupo_zfunc.rs"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// verification-helper: PROBLEM https://onlinejudge.u-aizu.ac.jp/problems/GRL_7_A
2+
3+
use proconio::input;
4+
use programming_team_code_rust::graphs::hopcroft_karp::HopcroftKarp;
5+
6+
mod hopcroft_karp_asserts;
7+
use hopcroft_karp_asserts::hopcroft_karp_asserts;
8+
9+
fn main() {
10+
input! {
11+
lsz: usize,
12+
rsz: usize,
13+
m: usize,
14+
edge_list: [(usize, usize); m],
15+
}
16+
17+
let mut adj = vec![vec![]; lsz];
18+
for &(u, v) in edge_list.iter() {
19+
adj[u].push(v);
20+
}
21+
22+
let HopcroftKarp {
23+
matching_siz,
24+
l_to_r,
25+
r_to_l,
26+
mvc_l,
27+
mvc_r,
28+
} = HopcroftKarp::new(&adj, rsz);
29+
30+
hopcroft_karp_asserts(matching_siz, &l_to_r, &r_to_l, &mvc_l, &mvc_r, &edge_list);
31+
32+
println!("{}", matching_siz);
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
pub fn hopcroft_karp_asserts(
2+
matching_siz: usize,
3+
l_to_r: &[Option<usize>],
4+
r_to_l: &[Option<usize>],
5+
mvc_l: &[bool],
6+
mvc_r: &[bool],
7+
edge_list: &[(usize, usize)],
8+
) {
9+
for (left, right) in [(l_to_r, r_to_l), (r_to_l, l_to_r)] {
10+
assert_eq!(
11+
matching_siz,
12+
left.iter().filter(|elem| elem.is_some()).count()
13+
);
14+
for (i, elem) in left.iter().enumerate().filter(|(_, elem)| elem.is_some()) {
15+
assert_eq!(Some(i), right[elem.unwrap()]);
16+
}
17+
}
18+
19+
fn count_true(a: &[bool]) -> usize {
20+
a.iter().filter(|&&elem| elem).count()
21+
}
22+
assert_eq!(matching_siz, count_true(mvc_l) + count_true(mvc_r));
23+
24+
for &(u, v) in edge_list.iter() {
25+
// this might look weird, but it's done so that code coverage passes:
26+
//
27+
// this:
28+
// assert!(mvc_l[u] || mvc_r[v]);
29+
// will fail code coverage, saying that the false branch is never run
30+
let either = mvc_l[u] || mvc_r[v];
31+
assert!(either);
32+
}
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// verification-helper: PROBLEM https://judge.yosupo.jp/problem/bipartitematching
2+
3+
use proconio::input;
4+
use programming_team_code_rust::graphs::hopcroft_karp::HopcroftKarp;
5+
6+
mod hopcroft_karp_asserts;
7+
use hopcroft_karp_asserts::hopcroft_karp_asserts;
8+
9+
fn main() {
10+
input! {
11+
lsz: usize,
12+
rsz: usize,
13+
m: usize,
14+
edge_list: [(usize, usize); m],
15+
}
16+
17+
let mut adj = vec![vec![]; lsz];
18+
for &(u, v) in edge_list.iter() {
19+
adj[u].push(v);
20+
}
21+
22+
let HopcroftKarp {
23+
matching_siz,
24+
l_to_r,
25+
r_to_l,
26+
mvc_l,
27+
mvc_r,
28+
} = HopcroftKarp::new(&adj, rsz);
29+
30+
hopcroft_karp_asserts(matching_siz, &l_to_r, &r_to_l, &mvc_l, &mvc_r, &edge_list);
31+
32+
println!("{}", matching_siz);
33+
for (i, elem) in l_to_r.iter().enumerate().filter(|(_, elem)| elem.is_some()) {
34+
println!("{} {}", i, elem.unwrap());
35+
}
36+
}

src/graphs/hopcroft_karp.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! # Hopcroft Karp algorithm for Bipartite Matching
2+
3+
use std::collections::VecDeque;
4+
5+
/// # Example
6+
/// ```
7+
/// use programming_team_code_rust::graphs::hopcroft_karp::HopcroftKarp;
8+
///
9+
/// let mut adj = vec![vec![]; 2];
10+
/// for (u, v) in [(0, 0), (0, 2), (1, 2)] {
11+
/// adj[u].push(v);
12+
/// }
13+
///
14+
/// let HopcroftKarp {
15+
/// matching_siz,
16+
/// l_to_r,
17+
/// r_to_l,
18+
/// mvc_l,
19+
/// mvc_r,
20+
/// } = HopcroftKarp::new(&adj, 3);
21+
///
22+
/// assert_eq!(matching_siz, 2);
23+
/// assert_eq!(l_to_r, [Some(0), Some(2)]);
24+
/// assert_eq!(r_to_l, [Some(0), None, Some(1)]);
25+
/// assert_eq!(mvc_l, [true, true]);
26+
/// assert_eq!(mvc_r, [false, false, false]);
27+
/// ```
28+
///
29+
/// # Complexity
30+
/// - V: number of vertices; V = lsz + rsz
31+
/// - E: number of edges
32+
/// - Time: O(V + E * sqrt(v))
33+
/// - Space: O(V)
34+
pub struct HopcroftKarp {
35+
/// number of edges in matching
36+
pub matching_siz: usize,
37+
/// l_to_r\[u_left_side\] = Some(v_right_side) iff edge u_left_side <=> v_right_side is in matching
38+
pub l_to_r: Vec<Option<usize>>,
39+
/// r_to_l\[v_right_side\] = Some(u_left_side) iff edge u_left_side <=> v_right_side is in matching
40+
pub r_to_l: Vec<Option<usize>>,
41+
/// mvc_l\[u_left_side\] = true iff u_left_side is in min vertex cover
42+
pub mvc_l: Vec<bool>,
43+
/// mvc_r\[v_right_side\] = true iff v_right_side is in min vertex cover
44+
pub mvc_r: Vec<bool>,
45+
}
46+
47+
impl HopcroftKarp {
48+
/// Calculates a max matching and min vertex cover
49+
pub fn new(adj: &[Vec<usize>], rsz: usize) -> Self {
50+
let lsz = adj.len();
51+
let mut e = HopcroftKarp {
52+
matching_siz: 0,
53+
l_to_r: vec![None; lsz],
54+
r_to_l: vec![None; rsz],
55+
mvc_l: vec![false; lsz],
56+
mvc_r: vec![false; rsz],
57+
};
58+
loop {
59+
let mut dist = vec![usize::MAX; lsz];
60+
let mut q = VecDeque::new();
61+
for (i, _) in e
62+
.l_to_r
63+
.iter()
64+
.enumerate()
65+
.filter(|&(_, elem)| elem.is_none())
66+
{
67+
dist[i] = 0;
68+
q.push_back(i);
69+
}
70+
let mut found = false;
71+
for v in &mut e.mvc_l {
72+
*v = true;
73+
}
74+
for v in &mut e.mvc_r {
75+
*v = false;
76+
}
77+
while let Some(u) = q.pop_front() {
78+
e.mvc_l[u] = false;
79+
for &v in &adj[u] {
80+
e.mvc_r[v] = true;
81+
if let Some(w) = e.r_to_l[v] {
82+
if dist[w] > 1 + dist[u] {
83+
dist[w] = 1 + dist[u];
84+
q.push_back(w);
85+
}
86+
} else {
87+
found = true;
88+
}
89+
}
90+
}
91+
if !found {
92+
return e;
93+
}
94+
fn dfs(u: usize, adj: &[Vec<usize>], dist: &mut [usize], e: &mut HopcroftKarp) -> bool {
95+
for &v in &adj[u] {
96+
let w = e.r_to_l[v];
97+
if w.is_none()
98+
|| dist[u] + 1 == dist[w.unwrap()] && dfs(w.unwrap(), adj, dist, e)
99+
{
100+
(e.l_to_r[u], e.r_to_l[v]) = (Some(v), Some(u));
101+
return true;
102+
}
103+
}
104+
dist[u] = usize::MAX;
105+
false
106+
}
107+
e.matching_siz += (0..lsz)
108+
.filter(|&i| e.l_to_r[i].is_none() && dfs(i, adj, &mut dist, &mut e))
109+
.count();
110+
}
111+
}
112+
}

src/graphs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod cuts;
77
mod dfs_order;
88
pub mod dijk;
99
pub mod hld;
10+
pub mod hopcroft_karp;
1011
pub mod lca;
1112
pub mod scc;
1213
pub mod two_sat;

0 commit comments

Comments
 (0)