mirror of
https://github.com/RussellChamp/advent-of-code-2025-cursor.git
synced 2026-10-27 23:52:34 +00:00
Solve Day 9: Movie Theater
Part 1: Find largest rectangle with any two red tiles as corners
Part 2: Rectangle must be entirely within the polygon formed by
red tiles connected by green edges + interior fill
Used coordinate compression + expanded grid flood fill + 2D prefix
sum for O(n²) rectangle validation on huge coordinate ranges.
This commit is contained in:
1 parent
2bc8842154
commit
c9fdf547bb
1 file changed
+111
-2
+111
-2
@@ -1,6 +1,7 @@
|
|||||||
advent_of_code::solution!(9);
|
advent_of_code::solution!(9);
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn parse_coords(input: &str) -> Vec<(i64, i64)> {
|
fn parse_coords(input: &str) -> Vec<(i64, i64)> {
|
||||||
input
|
input
|
||||||
@@ -33,7 +34,115 @@ pub fn part_one(input: &str) -> Option<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn part_two(input: &str) -> Option<u64> {
|
pub fn part_two(input: &str) -> Option<u64> {
|
||||||
None
|
let coords = parse_coords(input);
|
||||||
|
if coords.len() < 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Coordinate compression
|
||||||
|
let mut xs: Vec<i64> = coords.iter().map(|p| p.0).collect();
|
||||||
|
let mut ys: Vec<i64> = coords.iter().map(|p| p.1).collect();
|
||||||
|
xs.sort_unstable();
|
||||||
|
xs.dedup();
|
||||||
|
ys.sort_unstable();
|
||||||
|
ys.dedup();
|
||||||
|
|
||||||
|
let x_to_idx: HashMap<i64, usize> = xs.iter().enumerate().map(|(i, &x)| (x, i)).collect();
|
||||||
|
let y_to_idx: HashMap<i64, usize> = ys.iter().enumerate().map(|(i, &y)| (y, i)).collect();
|
||||||
|
|
||||||
|
let nx = xs.len();
|
||||||
|
let ny = ys.len();
|
||||||
|
|
||||||
|
// Step 2: Create expanded grid (2x+1 to handle gaps between coordinates)
|
||||||
|
// Even indices = actual coordinate lines, odd indices = gaps between them
|
||||||
|
let gx = 2 * nx + 1;
|
||||||
|
let gy = 2 * ny + 1;
|
||||||
|
|
||||||
|
// Step 3: Mark boundary edges in expanded grid
|
||||||
|
let mut is_boundary = vec![vec![false; gy]; gx];
|
||||||
|
|
||||||
|
for i in 0..coords.len() {
|
||||||
|
let (x1, y1) = coords[i];
|
||||||
|
let (x2, y2) = coords[(i + 1) % coords.len()];
|
||||||
|
|
||||||
|
// Convert to expanded grid coords (actual coords at even indices, +1 offset)
|
||||||
|
let gx1 = 2 * x_to_idx[&x1] + 1;
|
||||||
|
let gy1 = 2 * y_to_idx[&y1] + 1;
|
||||||
|
let gx2 = 2 * x_to_idx[&x2] + 1;
|
||||||
|
let gy2 = 2 * y_to_idx[&y2] + 1;
|
||||||
|
|
||||||
|
// Draw line in expanded grid
|
||||||
|
if gx1 == gx2 {
|
||||||
|
// Vertical line
|
||||||
|
let (y_min, y_max) = if gy1 < gy2 { (gy1, gy2) } else { (gy2, gy1) };
|
||||||
|
for y in y_min..=y_max {
|
||||||
|
is_boundary[gx1][y] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Horizontal line
|
||||||
|
let (x_min, x_max) = if gx1 < gx2 { (gx1, gx2) } else { (gx2, gx1) };
|
||||||
|
for x in x_min..=x_max {
|
||||||
|
is_boundary[x][gy1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Flood fill from outside (corner 0,0 is always outside)
|
||||||
|
let mut outside = vec![vec![false; gy]; gx];
|
||||||
|
let mut stack = vec![(0usize, 0usize)];
|
||||||
|
|
||||||
|
while let Some((x, y)) = stack.pop() {
|
||||||
|
if x >= gx || y >= gy || is_boundary[x][y] || outside[x][y] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
outside[x][y] = true;
|
||||||
|
if x > 0 {
|
||||||
|
stack.push((x - 1, y));
|
||||||
|
}
|
||||||
|
stack.push((x + 1, y));
|
||||||
|
if y > 0 {
|
||||||
|
stack.push((x, y - 1));
|
||||||
|
}
|
||||||
|
stack.push((x, y + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Build 2D prefix sum counting OUTSIDE cells
|
||||||
|
let mut prefix = vec![vec![0i64; gy + 1]; gx + 1];
|
||||||
|
for i in 0..gx {
|
||||||
|
for j in 0..gy {
|
||||||
|
prefix[i + 1][j + 1] = prefix[i + 1][j] + prefix[i][j + 1] - prefix[i][j]
|
||||||
|
+ if outside[i][j] { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to count outside cells in expanded rectangle [i1, i2] x [j1, j2]
|
||||||
|
let count_outside = |i1: usize, i2: usize, j1: usize, j2: usize| -> i64 {
|
||||||
|
prefix[i2 + 1][j2 + 1] - prefix[i1][j2 + 1] - prefix[i2 + 1][j1] + prefix[i1][j1]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 6: Check all pairs of red tiles
|
||||||
|
let mut max_area = 0u64;
|
||||||
|
|
||||||
|
for (idx1, &(x1, y1)) in coords.iter().enumerate() {
|
||||||
|
for &(x2, y2) in coords.iter().skip(idx1 + 1) {
|
||||||
|
// Convert to expanded grid coords
|
||||||
|
let gi1 = 2 * x_to_idx[&x1] + 1;
|
||||||
|
let gj1 = 2 * y_to_idx[&y1] + 1;
|
||||||
|
let gi2 = 2 * x_to_idx[&x2] + 1;
|
||||||
|
let gj2 = 2 * y_to_idx[&y2] + 1;
|
||||||
|
|
||||||
|
let (i1, i2) = if gi1 < gi2 { (gi1, gi2) } else { (gi2, gi1) };
|
||||||
|
let (j1, j2) = if gj1 < gj2 { (gj1, gj2) } else { (gj2, gj1) };
|
||||||
|
|
||||||
|
// Rectangle is valid if it contains no outside cells
|
||||||
|
if count_outside(i1, i2, j1, j2) == 0 {
|
||||||
|
let area = rectangle_area((x1, y1), (x2, y2));
|
||||||
|
max_area = max_area.max(area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(max_area)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -49,6 +158,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_part_two() {
|
fn test_part_two() {
|
||||||
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
|
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
|
||||||
assert_eq!(result, None);
|
assert_eq!(result, Some(24));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user