use rustc_hash::FxHashSet;
pub(super) fn minimum_vertex_cover_separator(
n: usize,
edges: &[(u32, u32)],
part: &[u8],
) -> SeparatorResult {
let mut is_boundary_a = vec![false; n];
let mut is_boundary_b = vec![false; n];
let mut cross_edges: Vec<(u32, u32)> = Vec::new();
for &(u, v) in edges {
if part[u as usize] != part[v as usize] {
let (a, b) = if part[u as usize] == 0 {
(u, v)
} else {
(v, u)
};
is_boundary_a[a as usize] = true;
is_boundary_b[b as usize] = true;
cross_edges.push((a, b));
}
}
let boundary_a: Vec<u32> = (0..n as u32)
.filter(|&i| is_boundary_a[i as usize])
.collect();
let boundary_b: Vec<u32> = (0..n as u32)
.filter(|&i| is_boundary_b[i as usize])
.collect();
let la = boundary_a.len();
let lb = boundary_b.len();
let mut idx_a = vec![None; n];
let mut idx_b = vec![None; n];
for (i, &v) in boundary_a.iter().enumerate() {
idx_a[v as usize] = Some(i);
}
for (i, &v) in boundary_b.iter().enumerate() {
idx_b[v as usize] = Some(i);
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); la];
for &(a, b) in &cross_edges {
let ai = idx_a[a as usize].expect("cross-edge endpoint is in the left boundary");
let bi = idx_b[b as usize].expect("cross-edge endpoint is in the right boundary");
adj[ai].push(bi);
}
for list in &mut adj {
list.sort_unstable();
list.dedup();
}
let mut match_l = vec![None; la];
let mut match_r = vec![None; lb];
let mut visited_r: Vec<bool> = vec![false; lb];
for u in 0..la {
visited_r.fill(false);
try_kuhn(u, &adj, &mut visited_r, &mut match_l, &mut match_r);
}
let mut in_z_l = vec![false; la];
let mut in_z_r = vec![false; lb];
let mut queue: Vec<usize> = Vec::new();
for u in 0..la {
if match_l[u].is_none() {
in_z_l[u] = true;
queue.push(u);
}
}
while let Some(u) = queue.pop() {
for &v in &adj[u] {
if match_l[u] == Some(v) {
continue;
}
if !in_z_r[v] {
in_z_r[v] = true;
if let Some(matched) = match_r[v]
&& !in_z_l[matched]
{
in_z_l[matched] = true;
queue.push(matched);
}
}
}
}
let mut separator: Vec<u32> = Vec::new();
for (i, &v) in boundary_a.iter().enumerate() {
if !in_z_l[i] {
separator.push(v);
}
}
for (i, &v) in boundary_b.iter().enumerate() {
if in_z_r[i] {
separator.push(v);
}
}
let sep_set: FxHashSet<u32> = separator.iter().copied().collect();
let mut side_a: Vec<u32> = Vec::new();
let mut side_b: Vec<u32> = Vec::new();
for v in 0..n as u32 {
if sep_set.contains(&v) {
continue;
}
if part[v as usize] == 0 {
side_a.push(v);
} else {
side_b.push(v);
}
}
SeparatorResult {
side_a,
side_b,
separator,
}
}
pub(super) struct SeparatorResult {
pub side_a: Vec<u32>,
pub side_b: Vec<u32>,
pub separator: Vec<u32>,
}
struct KuhnFrame {
u: usize,
cursor: usize,
}
fn try_kuhn(
start: usize,
adj: &[Vec<usize>],
visited_r: &mut [bool],
match_l: &mut [Option<usize>],
match_r: &mut [Option<usize>],
) -> bool {
let mut stack = vec![KuhnFrame {
u: start,
cursor: 0,
}];
let mut reported: Option<bool> = None;
while !stack.is_empty() {
if reported.take() == Some(true) {
let level = stack.last().unwrap();
let (u, v) = (level.u, adj[level.u][level.cursor - 1]);
match_l[u] = Some(v);
match_r[v] = Some(u);
stack.pop();
reported = Some(true);
continue;
}
let top = stack.len() - 1;
let u = stack[top].u;
let mut stepped = false;
while stack[top].cursor < adj[u].len() {
let v = adj[u][stack[top].cursor];
stack[top].cursor += 1;
if visited_r[v] {
continue;
}
visited_r[v] = true;
if let Some(matched) = match_r[v] {
stack.push(KuhnFrame {
u: matched,
cursor: 0,
});
} else {
reported = Some(true);
}
stepped = true;
break;
}
if !stepped {
stack.pop();
reported = Some(false);
}
}
reported == Some(true)
}