diff --git a/src/bin/day9.rs b/src/bin/day9.rs index 990ea6d..f30a8e7 100644 --- a/src/bin/day9.rs +++ b/src/bin/day9.rs @@ -1,8 +1,9 @@ -use std::time::Instant; +use std::{collections::HashSet, ops::RangeInclusive, time::Instant}; use aoc_2025::{load, print_res}; use bstr::BString; +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] pub struct Point2 { x: u64, y: u64, @@ -26,22 +27,285 @@ pub fn parsing(input: &BString) -> color_eyre::Result { .collect() } +fn rect_area(a: &Point2, b: &Point2) -> u64 { + (a.x.abs_diff(b.x) + 1) * (a.y.abs_diff(b.y) + 1) +} + #[inline(never)] pub fn part1(input: Parsed) { let max_area = input .iter() .enumerate() .flat_map(|(ia, a)| input.iter().skip(ia + 1).map(move |b| (a, b))) - .map(|(a, b)| (a.x.abs_diff(b.x) + 1) * (a.y.abs_diff(b.y) + 1)) + .map(|(a, b)| rect_area(a, b)) .max() .unwrap(); print_res!("Max area: {max_area}"); } +struct Polygon { + points: Vec, + + x_values: Vec, + y_values: Vec, + + vertical_edges_by_x_slot: Vec>>, + horizontal_edges_by_y_slot: Vec>>, +} + +impl Polygon { + pub fn new(points: Vec) -> Self { + let x_values = { + let x: HashSet<_> = points.iter().map(|p| p.x).collect(); + let mut x: Vec<_> = x.into_iter().collect(); + x.sort_unstable(); + x + }; + + let y_values = { + let y: HashSet<_> = points.iter().map(|p| p.y).collect(); + let mut y: Vec<_> = y.into_iter().collect(); + y.sort_unstable(); + y + }; + + let mut this = Polygon { + points, + vertical_edges_by_x_slot: vec![Vec::new(); x_values.len()], + horizontal_edges_by_y_slot: vec![Vec::new(); y_values.len()], + x_values, + y_values, + }; + + for (i, p) in this.points.iter().enumerate() { + let prev = this.prev(i); + + if prev.x == p.x { + let x_stop = this.x_values.iter().position(|&x| x == p.x).unwrap(); + + // Vertical edge + let min_y = p.y.min(prev.y); + let max_y = p.y.max(prev.y); + + this.vertical_edges_by_x_slot[x_stop].push(min_y..=max_y); + } else { + let y_stop = this.y_values.iter().position(|&y| y == p.y).unwrap(); + + assert_eq!(prev.y, p.y); + + let min_x = p.x.min(prev.x); + let max_x = p.x.max(prev.x); + + this.horizontal_edges_by_y_slot[y_stop].push(min_x..=max_x); + } + } + + this + } + + pub fn contains(&self, point: Point2) -> bool { + // Is the point a corner? + if self.points.contains(&point) { + return true; + } + + // On a horizontal edge + if let Some(y_stop) = self.y_values.iter().position(|&y| y == point.y) { + let edges = &self.horizontal_edges_by_y_slot[y_stop]; + if edges.iter().any(|e| e.contains(&point.x)) { + return true; + } + } + + // On a vertical edge + if let Some(x_stop) = self.x_values.iter().position(|&x| x == point.x) { + let edges = &self.vertical_edges_by_x_slot[x_stop]; + if edges.iter().any(|e| e.contains(&point.y)) { + return true; + } + } + + // Not on the boundary + + let mut intersection_count = 0; + + // Check all vertical edges starting from this point + for (slot, _x) in self + .x_values + .iter() + .enumerate() + .skip_while(|&(_, &x)| x < point.x) + .take_while(|&(_, &x)| point.x <= x) + { + let edges = &self.vertical_edges_by_x_slot[slot]; + + if let Some(edge) = edges.iter().find(|r| r.contains(&point.y)) { + let intersect = if point.y == *edge.start() { + *edge.end() > point.y + } else if point.y == *edge.end() { + *edge.start() > point.y + } else { + true + }; + + if intersect { + intersection_count += 1; + } + } + } + + intersection_count % 2 == 1 + } + + pub fn points(&self) -> &[Point2] { + &self.points + } + + pub fn x_values(&self) -> &[u64] { + &self.x_values + } + + pub fn y_values(&self) -> &[u64] { + &self.y_values + } + + pub fn prev(&self, index: usize) -> Point2 { + if index == 0 { + *self.points.last().unwrap() + } else { + self.points[index - 1] + } + } +} + +fn horizontal_edges_contained(poly: &Polygon, a: Point2, b: Point2) -> bool { + assert_ne!(a.x, b.x); + + let min_x = a.x.min(b.x); + let max_x = a.x.max(b.x); + + let y_values: &[u64] = if a.y == b.y { &[a.y] } else { &[a.y, b.y] }; + + for &y in y_values { + // Left corner + + if !poly.contains(Point2 { x: min_x, y }) { + return false; + } + + if !poly.contains(Point2 { x: min_x + 1, y }) { + return false; + } + + // Inside edges + + for &x in poly + .x_values() + .iter() + .skip_while(|&&x| x <= min_x) + .take_while(|&&x| x < max_x) + { + if !poly.contains(Point2 { x, y }) + || !poly.contains(Point2 { x: x - 1, y }) + || !poly.contains(Point2 { x: x + 1, y }) + { + return false; + } + } + + // Right corner + + if !poly.contains(Point2 { x: max_x, y }) { + return false; + } + + if !poly.contains(Point2 { x: max_x - 1, y }) { + return false; + } + } + + true +} + +fn vertical_edges_contain(poly: &Polygon, a: Point2, b: Point2) -> bool { + assert_ne!(a.y, b.y); + + let min_y = a.y.min(b.y); + let max_y = a.y.max(b.y); + + let x_values: &[u64] = if a.x == b.x { &[a.x] } else { &[a.x, b.x] }; + + for &x in x_values { + // Bottom corner + + if !poly.contains(Point2 { x, y: min_y }) { + return false; + } + + if !poly.contains(Point2 { x, y: min_y + 1 }) { + return false; + } + + // Inside edges + + for &y in poly + .y_values() + .iter() + .skip_while(|&&y| y <= min_y) + .take_while(|&&y| y < max_y) + { + if !poly.contains(Point2 { x, y }) + || !poly.contains(Point2 { x, y: y - 1 }) + || !poly.contains(Point2 { x, y: y + 1 }) + { + return false; + } + } + + // Top corner + + if !poly.contains(Point2 { x, y: max_y }) { + return false; + } + + if !poly.contains(Point2 { x, y: max_y - 1 }) { + return false; + } + } + + true +} + #[inline(never)] pub fn part2(input: Parsed) { - todo!("todo part2") + let poly = Polygon::new(input); + + let mut max_area = 0; + + for (ia, &a) in poly.points().iter().enumerate() { + for &b in poly.points().iter().skip(ia + 1) { + let area = rect_area(&a, &b); + // Area would not even be bigger, don’t attempt to validate it + if area < max_area { + continue; + } + + assert!(area > 1); + + if a.x != b.x && !horizontal_edges_contained(&poly, a, b) { + continue; + } + + if a.y != b.y && !vertical_edges_contain(&poly, a, b) { + continue; + } + + max_area = area; + } + } + + print_res!("Max inside area: {max_area}"); } pub fn main() -> color_eyre::Result<()> {