problemreductions/models/misc/
rectilinear_picture_compression.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use serde::de::Deserializer;
12use serde::{Deserialize, Serialize};
13
14type Rectangle = (usize, usize, usize, usize);
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "RectilinearPictureCompression",
19 display_name: "Rectilinear Picture Compression",
20 aliases: &[],
21 dimensions: &[],
22 category: crate::registry::ProblemCategory::Misc,
23 module_path: module_path!(),
24 description: "Cover all 1-entries of a binary matrix with at most K axis-aligned all-1 rectangles",
25 fields: &[
26 FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "m x n binary matrix" },
27 FieldInfo { name: "bound", type_name: "i64", description: "Maximum number of rectangles allowed" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize)]
63pub struct RectilinearPictureCompression {
64 matrix: Vec<Vec<bool>>,
65 bound: i64,
66 #[serde(skip)]
67 maximal_rects: Vec<Rectangle>,
68}
69
70impl<'de> Deserialize<'de> for RectilinearPictureCompression {
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: Deserializer<'de>,
74 {
75 #[derive(Deserialize)]
76 struct Inner {
77 matrix: Vec<Vec<bool>>,
78 bound: i64,
79 }
80 let inner = Inner::deserialize(deserializer)?;
81 Ok(Self::new(inner.matrix, inner.bound))
82 }
83}
84
85impl RectilinearPictureCompression {
86 pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
92 assert!(!matrix.is_empty(), "Matrix must not be empty");
93 let cols = matrix[0].len();
94 assert!(cols > 0, "Matrix must have at least one column");
95 assert!(
96 matrix.iter().all(|row| row.len() == cols),
97 "All rows must have the same length"
98 );
99 let mut instance = Self {
100 matrix,
101 bound,
102 maximal_rects: Vec::new(),
103 };
104 instance.maximal_rects = instance.compute_maximal_rectangles();
105 instance
106 }
107
108 pub fn num_rows(&self) -> usize {
110 self.matrix.len()
111 }
112
113 pub fn num_cols(&self) -> usize {
115 self.matrix[0].len()
116 }
117
118 pub fn bound(&self) -> i64 {
120 self.bound
121 }
122
123 pub fn matrix(&self) -> &[Vec<bool>] {
125 &self.matrix
126 }
127
128 pub fn maximal_rectangles(&self) -> &[Rectangle] {
133 &self.maximal_rects
134 }
135
136 fn build_prefix_sum(&self) -> Vec<Vec<usize>> {
137 let m = self.num_rows();
138 let n = self.num_cols();
139 let mut prefix_sum = vec![vec![0; n + 1]; m + 1];
140
141 for r in 0..m {
142 let mut row_sum = 0;
143 for c in 0..n {
144 row_sum += usize::from(self.matrix[r][c]);
145 prefix_sum[r + 1][c + 1] = prefix_sum[r][c + 1] + row_sum;
146 }
147 }
148
149 prefix_sum
150 }
151
152 fn range_is_all_ones(
153 prefix_sum: &[Vec<usize>],
154 r1: usize,
155 c1: usize,
156 r2: usize,
157 c2: usize,
158 ) -> bool {
159 let area = (r2 - r1 + 1) * (c2 - c1 + 1);
160 let sum = prefix_sum[r2 + 1][c2 + 1] + prefix_sum[r1][c1]
161 - prefix_sum[r1][c2 + 1]
162 - prefix_sum[r2 + 1][c1];
163 sum == area
164 }
165
166 fn compute_maximal_rectangles(&self) -> Vec<Rectangle> {
171 let m = self.num_rows();
172 let n = self.num_cols();
173
174 let mut candidates = Vec::new();
177 for r1 in 0..m {
178 for c1 in 0..n {
179 if !self.matrix[r1][c1] {
180 continue;
181 }
182 let mut c_max = n;
184 for c in c1..n {
185 if !self.matrix[r1][c] {
186 c_max = c;
187 break;
188 }
189 }
190 let mut c_end = c_max; for r2 in r1..m {
193 let mut new_c_end = c1;
195 for c in c1..c_end {
196 if self.matrix[r2][c] {
197 new_c_end = c + 1;
198 } else {
199 break;
200 }
201 }
202 if new_c_end <= c1 {
203 break;
204 }
205 c_end = new_c_end;
206 candidates.push((r1, c1, r2, c_end - 1));
207 }
208 }
209 }
210
211 candidates.sort();
213 candidates.dedup();
214
215 let prefix_sum = self.build_prefix_sum();
218 candidates
219 .into_iter()
220 .filter(|&(r1, c1, r2, c2)| {
221 let can_extend_left =
222 c1 > 0 && Self::range_is_all_ones(&prefix_sum, r1, c1 - 1, r2, c1 - 1);
223 let can_extend_right =
224 c2 + 1 < n && Self::range_is_all_ones(&prefix_sum, r1, c2 + 1, r2, c2 + 1);
225 let can_extend_up =
226 r1 > 0 && Self::range_is_all_ones(&prefix_sum, r1 - 1, c1, r1 - 1, c2);
227 let can_extend_down =
228 r2 + 1 < m && Self::range_is_all_ones(&prefix_sum, r2 + 1, c1, r2 + 1, c2);
229
230 !(can_extend_left || can_extend_right || can_extend_up || can_extend_down)
231 })
232 .collect()
233 }
234}
235
236impl Problem for RectilinearPictureCompression {
237 const NAME: &'static str = "RectilinearPictureCompression";
238 type Solution = Vec<bool>;
239 type Value = crate::types::Or;
240
241 crate::problem_parameters![("num_cols", num_cols), ("num_rows", num_rows),];
242
243 fn variant() -> Vec<(&'static str, &'static str)> {
244 crate::variant_params![]
245 }
246
247 fn evaluate(
248 &self,
249 config: &Self::Solution,
250 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
251 Ok({
252 crate::types::Or({
253 let rects = &self.maximal_rects;
254 if config.len() != rects.len() {
255 return Err(crate::traits::EvaluationError::InvalidConfiguration(
256 "rectangle-selection length does not match the maximal rectangles".into(),
257 ));
258 }
259 let selected_count = config.iter().filter(|&&selected| selected).count();
261 let selected_count = i64::try_from(selected_count).map_err(|_| {
262 crate::traits::EvaluationError::IntegerOverflow(
263 "converting selected-rectangle count to i64".into(),
264 )
265 })?;
266 if selected_count > self.bound {
267 return Ok(crate::types::Or(false));
268 }
269
270 let m = self.num_rows();
272 let n = self.num_cols();
273 let mut covered = vec![vec![false; n]; m];
274 for (i, &x) in config.iter().enumerate() {
275 if x {
276 let (r1, c1, r2, c2) = rects[i];
277 for row in &mut covered[r1..=r2] {
278 for cell in &mut row[c1..=c2] {
279 *cell = true;
280 }
281 }
282 }
283 }
284
285 for (row_m, row_c) in self.matrix.iter().zip(covered.iter()) {
286 for (&entry, &cov) in row_m.iter().zip(row_c.iter()) {
287 if entry && !cov {
288 return Ok(crate::types::Or(false));
289 }
290 }
291 }
292
293 true
294 })
295 })
296 }
297}
298
299impl crate::solvers::BruteForceProblem for RectilinearPictureCompression {
300 fn dimensions(&self) -> Vec<usize> {
301 vec![2; self.maximal_rects.len()]
302 }
303}
304
305crate::declare_variants! {
306 default RectilinearPictureCompression => "2^(num_rows * num_cols)",
307}
308
309crate::register_brute_force! {
310 RectilinearPictureCompression decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
311}
312
313#[cfg(feature = "example-db")]
314pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
315 vec![crate::example_db::specs::ModelExampleSpec {
316 id: "rectilinear_picture_compression",
317 instance: Box::new(RectilinearPictureCompression::new(
321 vec![
322 vec![true, true, false, false],
323 vec![true, true, false, false],
324 vec![false, false, true, true],
325 vec![false, false, true, true],
326 ],
327 2,
328 )),
329 optimal_config: serde_json::json!(vec![true, true]),
330 optimal_value: serde_json::json!(true),
331 }]
332}
333
334#[cfg(test)]
335#[path = "../../unit_tests/models/misc/rectilinear_picture_compression.rs"]
336mod tests;