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 Self::try_new(inner.matrix, inner.bound).map_err(serde::de::Error::custom)
82 }
83}
84
85impl RectilinearPictureCompression {
86 pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
92 Self::try_new(matrix, bound).unwrap_or_else(|error| panic!("{error}"))
93 }
94
95 fn try_new(
96 matrix: Vec<Vec<bool>>,
97 bound: i64,
98 ) -> Result<Self, crate::registry::ConstructionError> {
99 if matrix.is_empty() {
100 return Err("Matrix must not be empty".into());
101 }
102 let cols = matrix[0].len();
103 if cols == 0 {
104 return Err("Matrix must have at least one column".into());
105 }
106 if matrix.iter().any(|row| row.len() != cols) {
107 return Err("All rows must have the same length".into());
108 }
109 let mut instance = Self {
110 matrix,
111 bound,
112 maximal_rects: Vec::new(),
113 };
114 instance.maximal_rects = instance.compute_maximal_rectangles();
115 Ok(instance)
116 }
117
118 pub fn num_rows(&self) -> usize {
120 self.matrix.len()
121 }
122
123 pub fn num_cols(&self) -> usize {
125 self.matrix[0].len()
126 }
127
128 pub fn bound(&self) -> i64 {
130 self.bound
131 }
132
133 pub fn matrix(&self) -> &[Vec<bool>] {
135 &self.matrix
136 }
137
138 pub fn maximal_rectangles(&self) -> &[Rectangle] {
143 &self.maximal_rects
144 }
145
146 pub fn num_rectangles(&self) -> usize {
148 self.maximal_rects.len()
149 }
150
151 pub fn total_rectangle_area(&self) -> usize {
153 self.maximal_rects
154 .iter()
155 .map(|&(r1, c1, r2, c2)| (r2 - r1 + 1) * (c2 - c1 + 1))
156 .sum()
157 }
158
159 fn build_prefix_sum(&self) -> Vec<Vec<usize>> {
160 let m = self.num_rows();
161 let n = self.num_cols();
162 let mut prefix_sum = vec![vec![0; n + 1]; m + 1];
163
164 for r in 0..m {
165 let mut row_sum = 0;
166 for c in 0..n {
167 row_sum += usize::from(self.matrix[r][c]);
168 prefix_sum[r + 1][c + 1] = prefix_sum[r][c + 1] + row_sum;
169 }
170 }
171
172 prefix_sum
173 }
174
175 fn range_is_all_ones(
176 prefix_sum: &[Vec<usize>],
177 r1: usize,
178 c1: usize,
179 r2: usize,
180 c2: usize,
181 ) -> bool {
182 let area = (r2 - r1 + 1) * (c2 - c1 + 1);
183 let sum = prefix_sum[r2 + 1][c2 + 1] + prefix_sum[r1][c1]
184 - prefix_sum[r1][c2 + 1]
185 - prefix_sum[r2 + 1][c1];
186 sum == area
187 }
188
189 fn compute_maximal_rectangles(&self) -> Vec<Rectangle> {
194 let m = self.num_rows();
195 let n = self.num_cols();
196
197 let mut candidates = Vec::new();
200 for r1 in 0..m {
201 for c1 in 0..n {
202 if !self.matrix[r1][c1] {
203 continue;
204 }
205 let mut c_max = n;
207 for c in c1..n {
208 if !self.matrix[r1][c] {
209 c_max = c;
210 break;
211 }
212 }
213 let mut c_end = c_max; for r2 in r1..m {
216 let mut new_c_end = c1;
218 for c in c1..c_end {
219 if self.matrix[r2][c] {
220 new_c_end = c + 1;
221 } else {
222 break;
223 }
224 }
225 if new_c_end <= c1 {
226 break;
227 }
228 c_end = new_c_end;
229 candidates.push((r1, c1, r2, c_end - 1));
230 }
231 }
232 }
233
234 candidates.sort();
236 candidates.dedup();
237
238 let prefix_sum = self.build_prefix_sum();
241 candidates
242 .into_iter()
243 .filter(|&(r1, c1, r2, c2)| {
244 let can_extend_left =
245 c1 > 0 && Self::range_is_all_ones(&prefix_sum, r1, c1 - 1, r2, c1 - 1);
246 let can_extend_right =
247 c2 + 1 < n && Self::range_is_all_ones(&prefix_sum, r1, c2 + 1, r2, c2 + 1);
248 let can_extend_up =
249 r1 > 0 && Self::range_is_all_ones(&prefix_sum, r1 - 1, c1, r1 - 1, c2);
250 let can_extend_down =
251 r2 + 1 < m && Self::range_is_all_ones(&prefix_sum, r2 + 1, c1, r2 + 1, c2);
252
253 !(can_extend_left || can_extend_right || can_extend_up || can_extend_down)
254 })
255 .collect()
256 }
257}
258
259impl Problem for RectilinearPictureCompression {
260 const NAME: &'static str = "RectilinearPictureCompression";
261 type Solution = Vec<bool>;
262 type Value = crate::types::Or;
263
264 crate::problem_parameters![
265 ("num_cols", num_cols),
266 ("num_rows", num_rows),
267 ("num_rectangles", num_rectangles),
268 ("total_rectangle_area", total_rectangle_area),
269 ];
270
271 fn variant() -> Vec<(&'static str, &'static str)> {
272 crate::variant_params![]
273 }
274
275 fn evaluate(
276 &self,
277 config: &Self::Solution,
278 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
279 Ok({
280 crate::types::Or({
281 let rects = &self.maximal_rects;
282 if config.len() != rects.len() {
283 return Err(crate::traits::EvaluationError::InvalidConfiguration(
284 "rectangle-selection length does not match the maximal rectangles".into(),
285 ));
286 }
287 let selected_count = config.iter().filter(|&&selected| selected).count();
289 let selected_count = i64::try_from(selected_count).map_err(|_| {
290 crate::traits::EvaluationError::IntegerOverflow(
291 "converting selected-rectangle count to i64".into(),
292 )
293 })?;
294 if selected_count > self.bound {
295 return Ok(crate::types::Or(false));
296 }
297
298 let m = self.num_rows();
300 let n = self.num_cols();
301 let mut covered = vec![vec![false; n]; m];
302 for (i, &x) in config.iter().enumerate() {
303 if x {
304 let (r1, c1, r2, c2) = rects[i];
305 for row in &mut covered[r1..=r2] {
306 for cell in &mut row[c1..=c2] {
307 *cell = true;
308 }
309 }
310 }
311 }
312
313 for (row_m, row_c) in self.matrix.iter().zip(covered.iter()) {
314 for (&entry, &cov) in row_m.iter().zip(row_c.iter()) {
315 if entry && !cov {
316 return Ok(crate::types::Or(false));
317 }
318 }
319 }
320
321 true
322 })
323 })
324 }
325}
326
327impl crate::solvers::BruteForceProblem for RectilinearPictureCompression {
328 fn dimensions(&self) -> Vec<usize> {
329 vec![2; self.maximal_rects.len()]
330 }
331}
332
333crate::declare_variants! {
334 default RectilinearPictureCompression => "2^(num_rows * num_cols)",
335}
336
337crate::register_brute_force! {
338 RectilinearPictureCompression decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
339}
340
341#[cfg(feature = "example-db")]
342pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
343 vec![crate::example_db::specs::ModelExampleSpec {
344 id: "rectilinear_picture_compression",
345 instance: Box::new(RectilinearPictureCompression::new(
349 vec![
350 vec![true, true, false, false],
351 vec![true, true, false, false],
352 vec![false, false, true, true],
353 vec![false, false, true, true],
354 ],
355 2,
356 )),
357 optimal_config: serde_json::json!(vec![true, true]),
358 optimal_value: serde_json::json!(true),
359 }]
360}
361
362#[cfg(test)]
363#[path = "../../unit_tests/models/misc/rectilinear_picture_compression.rs"]
364mod tests;