Day 20
This commit is contained in:
parent
10245f3faa
commit
4f23e7001a
1 changed files with 178 additions and 0 deletions
178
src/bin/day20.rs
Normal file
178
src/bin/day20.rs
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aoc_2024::{load, neighbours, print_res};
|
||||||
|
use bstr::{BString, ByteSlice};
|
||||||
|
use color_eyre::eyre::{bail, ensure, OptionExt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Racetrack {
|
||||||
|
grid: Vec<Vec<bool>>,
|
||||||
|
end: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parsed = Racetrack;
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
|
||||||
|
let mut grid = Vec::new();
|
||||||
|
let mut end = None;
|
||||||
|
|
||||||
|
for (l, line) in input.lines().enumerate() {
|
||||||
|
let mut grid_line = Vec::new();
|
||||||
|
|
||||||
|
for (c, &v) in line.iter().enumerate() {
|
||||||
|
grid_line.push(match v {
|
||||||
|
b'.' | b'S' => true,
|
||||||
|
b'#' => false,
|
||||||
|
b'E' => {
|
||||||
|
ensure!(end.is_none(), "Duplicate end");
|
||||||
|
end = Some((l, c));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => bail!("Unknown character: {}", v as char),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.push(grid_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Racetrack {
|
||||||
|
grid,
|
||||||
|
end: end.ok_or_eyre("Missing end")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUTOFF: usize = 100;
|
||||||
|
|
||||||
|
impl Racetrack {
|
||||||
|
fn distances_from_end(&self) -> HashMap<(usize, usize), usize> {
|
||||||
|
let mut distances = HashMap::new();
|
||||||
|
|
||||||
|
let mut current = HashSet::new();
|
||||||
|
current.insert(self.end);
|
||||||
|
let mut dist = 0;
|
||||||
|
|
||||||
|
while !current.is_empty() {
|
||||||
|
let mut next = HashSet::new();
|
||||||
|
|
||||||
|
for (l, c) in current {
|
||||||
|
distances.insert((l, c), dist);
|
||||||
|
|
||||||
|
for (nl, nc) in neighbours(l, c, &self.grid) {
|
||||||
|
if self.grid[nl][nc] && !distances.contains_key(&(nl, nc)) {
|
||||||
|
next.insert((nl, nc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next;
|
||||||
|
dist += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
distances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn part1(input: Parsed) {
|
||||||
|
let distances = input.distances_from_end();
|
||||||
|
|
||||||
|
let mut saved_count = HashMap::<_, u64>::new();
|
||||||
|
|
||||||
|
for (&(cheat_l, cheat_c), &start_dist) in &distances {
|
||||||
|
for (nl, nc) in neighbours(cheat_l, cheat_c, &input.grid) {
|
||||||
|
for (nnl, nnc) in neighbours(nl, nc, &input.grid) {
|
||||||
|
if !input.grid[nnl][nnc] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_dist = distances[&(nnl, nnc)];
|
||||||
|
|
||||||
|
// Did not gain anything with this cheat
|
||||||
|
if start_dist + 2 >= end_dist {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let saved = end_dist - start_dist - 2;
|
||||||
|
*saved_count.entry(saved).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let major: u64 = saved_count
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(&s, &occ)| if s >= CUTOFF { Some(occ) } else { None })
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
print_res!("Number of ways to cheat saving 100ps: {major}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn part2(input: Parsed) {
|
||||||
|
let distances = input.distances_from_end();
|
||||||
|
|
||||||
|
let mut saved_count = HashMap::<_, u64>::new();
|
||||||
|
|
||||||
|
for (&(cheat_l, cheat_c), &start_dist) in &distances {
|
||||||
|
let min_l = cheat_l.saturating_sub(20);
|
||||||
|
let max_l = std::cmp::min(cheat_l + 20, input.grid.len() - 1);
|
||||||
|
|
||||||
|
let min_c = cheat_c.saturating_sub(20);
|
||||||
|
let max_c = std::cmp::min(cheat_c + 20, input.grid[0].len() - 1);
|
||||||
|
|
||||||
|
for el in min_l..=max_l {
|
||||||
|
for ec in min_c..=max_c {
|
||||||
|
let distance = cheat_l.abs_diff(el) + cheat_c.abs_diff(ec);
|
||||||
|
|
||||||
|
if distance > 20 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.grid[el][ec] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_dist = distances[&(el, ec)];
|
||||||
|
|
||||||
|
// Did not gain anything with this cheat
|
||||||
|
if start_dist + 20 >= end_dist {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let saved = end_dist - start_dist - distance;
|
||||||
|
*saved_count.entry(saved).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let major: u64 = saved_count
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(&s, &occ)| if s >= CUTOFF { Some(occ) } else { None })
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
print_res!("Number of ways to cheat saving 100ps: {major}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> color_eyre::Result<()> {
|
||||||
|
let context = load()?;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let parsed = parsing(&context.input)?;
|
||||||
|
let elapsed = humantime::format_duration(start.elapsed());
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
if context.part == 1 {
|
||||||
|
part1(parsed);
|
||||||
|
} else {
|
||||||
|
part2(parsed);
|
||||||
|
}
|
||||||
|
let elapsed_part = humantime::format_duration(start.elapsed());
|
||||||
|
|
||||||
|
println!(" Parsing: {elapsed}");
|
||||||
|
println!(" Solving: {elapsed_part}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue