|
| 1 | +//! Reduction from ExactCoverBy3Sets to BoundedDiameterSpanningTree. |
| 2 | +//! |
| 3 | +//! Given an X3C instance with universe X (|X| = 3q) and collection |
| 4 | +//! C = [S_0, ..., S_{m-1}] of 3-element subsets of X, build a weighted graph |
| 5 | +//! and parameters (B, D) so that the BDST instance is feasible iff the source |
| 6 | +//! has an exact cover. |
| 7 | +//! |
| 8 | +//! Construction (Garey & Johnson, ND4): |
| 9 | +//! |
| 10 | +//! * Vertex set V = {r, v_1, v_2} ∪ {s_0, ..., s_{m-1}} ∪ {e_0, ..., e_{3q-1}}. |
| 11 | +//! Indices: r = 0, v_1 = 1, v_2 = 2, s_i = 3 + i, e_j = 3 + m + j. |
| 12 | +//! * Edges (all weights ∈ {1, 2}): |
| 13 | +//! - Forced-center path: (r, v_1) weight 1, (v_1, v_2) weight 1. |
| 14 | +//! - Root-to-set: (r, s_i) weight 2 for every i ∈ [0, m). |
| 15 | +//! - Set-to-element: (s_i, e_j) weight 1 whenever j ∈ S_i. |
| 16 | +//! - Set clique: (s_i, s_j) weight 1 for all 0 ≤ i < j < m. |
| 17 | +//! * Diameter bound D = 4. |
| 18 | +//! * Weight bound B = 4q + m + 2. |
| 19 | +//! |
| 20 | +//! Forward direction: an exact cover C' = {S_{i_1}, ..., S_{i_q}} yields a |
| 21 | +//! spanning tree of total weight 2q + (m - q) + 3q + 2 = 4q + m + 2 = B and |
| 22 | +//! every vertex within distance 2 of r, so the diameter is ≤ 4. |
| 23 | +//! |
| 24 | +//! Backward direction: any feasible spanning tree must keep every vertex |
| 25 | +//! within distance 2 of r (otherwise dist to v_2 exceeds 4). Element vertices |
| 26 | +//! only attach through set vertices, so each e_j sits at depth 2 below some |
| 27 | +//! s_i that is directly attached to r. Budget counting then forces the number |
| 28 | +//! of root-to-set edges to be exactly q, and pigeonhole on the 3q element |
| 29 | +//! attachments forces the q chosen sets to be pairwise disjoint -- an exact |
| 30 | +//! cover. |
| 31 | +//! |
| 32 | +//! The solution extractor reads the binary indicator of each root-to-set edge. |
| 33 | +
|
| 34 | +use crate::models::graph::BoundedDiameterSpanningTree; |
| 35 | +use crate::models::set::ExactCoverBy3Sets; |
| 36 | +use crate::reduction; |
| 37 | +use crate::rules::traits::{ReduceTo, ReductionResult}; |
| 38 | +use crate::topology::SimpleGraph; |
| 39 | + |
| 40 | +/// Result of reducing ExactCoverBy3Sets to BoundedDiameterSpanningTree. |
| 41 | +#[derive(Debug, Clone)] |
| 42 | +pub struct ReductionX3CToBoundedDiameterSpanningTree { |
| 43 | + target: BoundedDiameterSpanningTree<SimpleGraph, i32>, |
| 44 | + source_num_subsets: usize, |
| 45 | +} |
| 46 | + |
| 47 | +impl ReductionResult for ReductionX3CToBoundedDiameterSpanningTree { |
| 48 | + type Source = ExactCoverBy3Sets; |
| 49 | + type Target = BoundedDiameterSpanningTree<SimpleGraph, i32>; |
| 50 | + |
| 51 | + fn target_problem(&self) -> &Self::Target { |
| 52 | + &self.target |
| 53 | + } |
| 54 | + |
| 55 | + /// Extract the chosen source subsets from the spanning-tree configuration. |
| 56 | + /// |
| 57 | + /// The construction places the m root-to-set edges (r, s_i) at edge indices |
| 58 | + /// 2..2+m (right after the forced-center path edges). For a YES-instance, |
| 59 | + /// the optimal target witness selects exactly q of these edges, which |
| 60 | + /// correspond to the q chosen subsets. |
| 61 | + fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> { |
| 62 | + let m = self.source_num_subsets; |
| 63 | + let root_to_set_offset = 2; |
| 64 | + (0..m) |
| 65 | + .map(|i| { |
| 66 | + usize::from( |
| 67 | + target_solution |
| 68 | + .get(root_to_set_offset + i) |
| 69 | + .copied() |
| 70 | + .unwrap_or(0) |
| 71 | + == 1, |
| 72 | + ) |
| 73 | + }) |
| 74 | + .collect() |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +#[reduction(overhead = { |
| 79 | + num_vertices = "num_subsets + universe_size + 3", |
| 80 | + num_edges = "2 + 4 * num_subsets + num_subsets * (num_subsets - 1) / 2", |
| 81 | + weight_bound = "4 * universe_size / 3 + num_subsets + 2", |
| 82 | + diameter_bound = "4", |
| 83 | +})] |
| 84 | +impl ReduceTo<BoundedDiameterSpanningTree<SimpleGraph, i32>> for ExactCoverBy3Sets { |
| 85 | + type Result = ReductionX3CToBoundedDiameterSpanningTree; |
| 86 | + |
| 87 | + fn reduce_to(&self) -> Self::Result { |
| 88 | + let universe_size = self.universe_size(); |
| 89 | + let m = self.num_subsets(); |
| 90 | + let q = universe_size / 3; |
| 91 | + |
| 92 | + // Vertex indexing matches the docstring. |
| 93 | + let s_index = |i: usize| 3 + i; |
| 94 | + let e_index = |j: usize| 3 + m + j; |
| 95 | + let num_vertices = 3 + m + universe_size; |
| 96 | + |
| 97 | + let mut edges: Vec<(usize, usize)> = Vec::new(); |
| 98 | + let mut weights: Vec<i32> = Vec::new(); |
| 99 | + |
| 100 | + // Forced-center path edges (indices 0 and 1). |
| 101 | + edges.push((0, 1)); // (r, v_1) |
| 102 | + weights.push(1); |
| 103 | + edges.push((1, 2)); // (v_1, v_2) |
| 104 | + weights.push(1); |
| 105 | + |
| 106 | + // Root-to-set edges (indices 2..2+m). The extractor relies on this layout. |
| 107 | + for i in 0..m { |
| 108 | + edges.push((0, s_index(i))); |
| 109 | + weights.push(2); |
| 110 | + } |
| 111 | + |
| 112 | + // Set-to-element edges. Subsets are already sorted in `ExactCoverBy3Sets::new`. |
| 113 | + for (i, subset) in self.subsets().iter().enumerate() { |
| 114 | + for &j in subset { |
| 115 | + edges.push((s_index(i), e_index(j))); |
| 116 | + weights.push(1); |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + // Set clique edges. |
| 121 | + for i in 0..m { |
| 122 | + for j in (i + 1)..m { |
| 123 | + edges.push((s_index(i), s_index(j))); |
| 124 | + weights.push(1); |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + let weight_bound: i32 = (4 * q + m + 2) as i32; |
| 129 | + let diameter_bound: usize = 4; |
| 130 | + |
| 131 | + let graph = SimpleGraph::new(num_vertices, edges); |
| 132 | + let target = BoundedDiameterSpanningTree::new(graph, weights, weight_bound, diameter_bound); |
| 133 | + |
| 134 | + ReductionX3CToBoundedDiameterSpanningTree { |
| 135 | + target, |
| 136 | + source_num_subsets: m, |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +#[cfg(feature = "example-db")] |
| 142 | +pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> { |
| 143 | + use crate::export::SolutionPair; |
| 144 | + |
| 145 | + vec![crate::example_db::specs::RuleExampleSpec { |
| 146 | + id: "exactcoverby3sets_to_boundeddiameterspanningtree", |
| 147 | + build: || { |
| 148 | + // q = 2, m = 2: X = {0..5}, C = [{0,1,2}, {3,4,5}]. |
| 149 | + // Exact cover: both subsets. Target has 11 vertices and 11 edges, |
| 150 | + // so brute-force solving stays well under the 5s test budget. |
| 151 | + let source = ExactCoverBy3Sets::new(6, vec![[0, 1, 2], [3, 4, 5]]); |
| 152 | + |
| 153 | + // Source: select both subsets. |
| 154 | + let source_config = vec![1, 1]; |
| 155 | + |
| 156 | + // Target spanning tree (n = 11, n-1 = 10 edges): |
| 157 | + // (r,v1)=idx 0, (v1,v2)=idx 1, (r,s0)=idx 2, (r,s1)=idx 3, |
| 158 | + // (s0,e0)=idx 4, (s0,e1)=idx 5, (s0,e2)=idx 6, |
| 159 | + // (s1,e3)=idx 7, (s1,e4)=idx 8, (s1,e5)=idx 9, |
| 160 | + // (s0,s1)=idx 10 (unused clique edge). |
| 161 | + // |
| 162 | + // Layout (from `reduce_to`): |
| 163 | + // edge order = forced(2) + root-to-set(m=2) + set-to-element(6) + clique(1) |
| 164 | + // = indices 0..1, 2..3, 4..9, 10 |
| 165 | + // Select every edge except the clique edge. |
| 166 | + let target_config = vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]; |
| 167 | + |
| 168 | + crate::example_db::specs::rule_example_with_witness::< |
| 169 | + _, |
| 170 | + BoundedDiameterSpanningTree<SimpleGraph, i32>, |
| 171 | + >( |
| 172 | + source, |
| 173 | + SolutionPair { |
| 174 | + source_config, |
| 175 | + target_config, |
| 176 | + }, |
| 177 | + ) |
| 178 | + }, |
| 179 | + }] |
| 180 | +} |
| 181 | + |
| 182 | +#[cfg(test)] |
| 183 | +#[path = "../unit_tests/rules/exactcoverby3sets_boundeddiameterspanningtree.rs"] |
| 184 | +mod tests; |
0 commit comments