aoc/year2025/
day09.rs

1//! # Movie Theater
2use crate::util::iter::*;
3use crate::util::parse::*;
4
5type Tile = [u32; 2];
6
7struct Candidate {
8    x: u32,
9    y: u32,
10    interval: Interval,
11}
12
13/// The set { x in u32 | l <= x <= r }.
14#[derive(Clone, Copy)]
15struct Interval {
16    l: u32,
17    r: u32,
18}
19
20impl Interval {
21    fn new(l: u32, r: u32) -> Self {
22        debug_assert!(l <= r);
23
24        Interval { l, r }
25    }
26
27    fn intersects(self, other: Self) -> bool {
28        other.l <= self.r && self.l <= other.r
29    }
30
31    fn intersection(self, other: Self) -> Self {
32        debug_assert!(self.intersects(other));
33
34        Interval::new(self.l.max(other.l), self.r.min(other.r))
35    }
36
37    fn contains(self, x: u32) -> bool {
38        self.l <= x && x <= self.r
39    }
40}
41
42pub fn parse(input: &str) -> Vec<Tile> {
43    let mut tiles: Vec<_> = input.iter_unsigned::<u32>().chunk::<2>().collect();
44    tiles.sort_unstable_by_key(|&[x, y]| (y, x));
45    tiles
46}
47
48pub fn part1(tiles: &[Tile]) -> u64 {
49    let (top_left_tiles, top_right_tiles) = get_potential_left_corner_tiles(tiles.iter().copied());
50    let (bottom_left_tiles, bottom_right_tiles) =
51        get_potential_left_corner_tiles(tiles.iter().copied().rev());
52
53    find_largest_from_all_corners(&top_left_tiles, &bottom_right_tiles)
54        .max(find_largest_from_all_corners(&bottom_left_tiles, &top_right_tiles))
55}
56
57/// This function filters `sorted_tiles` into two lists, one containing all tiles that could be the top left
58/// corner of the largest rectangle (assuming the largest rectangle has a top left corner), and the second
59/// containing all tiles that could be the top right corner.
60///
61/// It assumes `sorted_tiles` is sorted in ascending "y" values, or, to get the top right and bottom right corners,
62/// that `sorted_tiles` is sorted in descending "y" order.
63///
64/// It works (for the top left corners, for illustration) by only returning tiles (from the set of all tiles, "T") within
65/// the region:
66///
67///   R = { (x, y) ∈ ℝ² : ∀ (tx, ty) ∈ T, tx ≤ x ⇒ ty ≥ y }
68///
69/// Tiles outside of this region cannot possibly be a corner of the largest rectangle. Assume, for proof by contradiction,
70/// that the top left corner of the largest rectangle is in the complement of the set "R":
71///
72///   R' = { (x, y) ∈ ℝ² : ¬ (∀ (tx, ty) ∈ T, tx ≤ x ⇒ ty ≥ y) }
73///      = { (x, y) ∈ ℝ² : ∃ (tx, ty) ∈ T, tx ≤ x ∧ ty < y }
74///
75/// That is, for the corner (x, y), there exists another tile (tx, ty) that is to the left and above the corner tile, which
76/// means the tile isn't the corner of the largest possible rectangle, completing the proof by contradiction.
77///
78/// The `top_tiles` and `bottom_tiles` are the corner points of this region `R`, built up by scanning through tiles
79/// in either left to right or right to left order.
80fn get_potential_left_corner_tiles(
81    sorted_tiles: impl Iterator<Item = [u32; 2]>,
82) -> (Vec<[u32; 2]>, Vec<[u32; 2]>) {
83    let mut left_tiles = Vec::new();
84    let mut left_tiles_last_x = u32::MAX;
85
86    let mut right_tiles = Vec::new();
87    let mut right_tiles_last_x = u32::MIN;
88
89    let mut it = sorted_tiles.peekable();
90
91    while let Some(first_in_row) = it.next() {
92        let mut last_in_row = first_in_row;
93
94        while let Some(p) = it.next_if(|p| p[1] == first_in_row[1]) {
95            last_in_row = p;
96        }
97
98        let (y, left_x, right_x) = (
99            first_in_row[1],
100            first_in_row[0].min(last_in_row[0]),
101            first_in_row[0].max(last_in_row[0]),
102        );
103
104        if left_x < left_tiles_last_x {
105            left_tiles.push([left_x, y]);
106            left_tiles_last_x = left_x;
107        }
108
109        if right_x > right_tiles_last_x {
110            right_tiles.push([right_x, y]);
111            right_tiles_last_x = right_x;
112        }
113    }
114
115    (left_tiles, right_tiles)
116}
117
118#[inline]
119fn find_largest_from_all_corners(corner: &[[u32; 2]], opposite_corner: &[[u32; 2]]) -> u64 {
120    let mut largest = 0_u64;
121
122    for &p in corner {
123        for &q in opposite_corner {
124            largest =
125                largest.max((p[0].abs_diff(q[0]) + 1) as u64 * (p[1].abs_diff(q[1]) + 1) as u64);
126        }
127    }
128
129    largest
130}
131
132pub fn part2(tiles: &[Tile]) -> u64 {
133    // Track the largest area so far during scanning:
134    let mut largest_area: u64 = 0;
135
136    // Each red tile (`x`, `y`) becomes a candidate for being a top corner of the largest area, and during the
137    // scan, the `interval` containing the maximum possible width is updated:
138    let mut candidates: Vec<Candidate> = Vec::with_capacity(512);
139
140    // Maintain an ordered list of descending edges, i.e. [begin_interval_0, end_interval_0, begin_interval_1, end_interval_1, ...]:
141    let mut descending_edges: Vec<u32> = vec![];
142    let mut intervals_from_descending_edges = vec![];
143
144    // Invariants on the input data (defined by the puzzle) result in points arriving in pairs on the same y line:
145    let mut it = tiles.iter();
146
147    while let (Some(&[x0, y]), Some(&[x1, y1])) = (it.next(), it.next()) {
148        debug_assert_eq!(y, y1);
149
150        // Update the descending edges; since we are scanning from top to bottom, and within each line left to right,
151        // when we, starting from outside of the region, hit a corner tile it is either:
152        //
153        // - The corner of two edges, one going right and one going down. In this case, the `descending_edges` won't contain
154        //   the `x` coordinate, and we should "toggle" it on to denote that there is a new descending edge.
155        // - The corner of two edges, one going right and one going up. The `descending_edges` will contain an `x` coordinate
156        //   that should be "toggled" off.
157        //
158        // Similar arguments work for when we are scanning inside the edge and we hit the corner that ends the edge; this is also
159        // why corners always arrive in pairs.
160        //
161        // Do the update:
162        for x in [x0, x1] {
163            toggle_value_membership_in_ordered_list(&mut descending_edges, x);
164        }
165
166        // Every pair of descending edges in the ordered list defines a region; find the resulting intervals on this line:
167        update_intervals_from_descending_edges(
168            &descending_edges,
169            &mut intervals_from_descending_edges,
170        );
171
172        // Check the rectangles this red tile could be a bottom tile for, with the current candidates:
173        for candidate in &candidates {
174            for x in [x0, x1] {
175                if candidate.interval.contains(x) {
176                    largest_area = largest_area.max(
177                        (candidate.x.abs_diff(x) + 1) as u64 * (candidate.y.abs_diff(y) + 1) as u64,
178                    );
179                }
180            }
181        }
182
183        // Update candidates when their interval shrinks due to descending edge changes, and drop them when their interval becomes empty:
184        candidates.retain_mut(|candidate| {
185            if let Some(intersection_containing_x) =
186                intervals_from_descending_edges.iter().find(|i| i.contains(candidate.x))
187            {
188                candidate.interval = intersection_containing_x.intersection(candidate.interval);
189
190                true
191            } else {
192                false
193            }
194        });
195
196        // Add any new candidates:
197        for x in [x0, x1] {
198            if let Some(&containing) =
199                intervals_from_descending_edges.iter().find(|i| i.contains(x))
200            {
201                candidates.push(Candidate { x, y, interval: containing });
202            }
203        }
204    }
205
206    largest_area
207}
208
209// Adds `value` if it isn't in `ordered_list`, removes it if it is, maintaining the order.
210fn toggle_value_membership_in_ordered_list(ordered_list: &mut Vec<u32>, value: u32) {
211    match ordered_list.binary_search(&value) {
212        Ok(i) => {
213            ordered_list.remove(i);
214        }
215        Err(i) => {
216            ordered_list.insert(i, value);
217        }
218    }
219}
220
221// Changes the list of descending edges, [begin_interval_0, end_interval_0, begin_interval_1, end_interval_1, ...],
222// into a vector containing the intervals.
223#[inline]
224fn update_intervals_from_descending_edges(descending_edges: &[u32], to_update: &mut Vec<Interval>) {
225    debug_assert!(descending_edges.len().is_multiple_of(2));
226
227    to_update.clear();
228
229    let mut it = descending_edges.iter();
230
231    while let (Some(&l), Some(&r)) = (it.next(), it.next()) {
232        to_update.push(Interval::new(l, r));
233    }
234}