#![allow(clippy::cast_possible_wrap)] use std::{ collections::{HashSet, VecDeque}, io::Read, }; use indexmap::IndexMap; use itertools::Itertools; fn main() -> anyhow::Result<()> { let mut input = String::new(); std::io::stdin().read_to_string(&mut input)?; let input = parse_input(&input); let distances = dists(&input); part1(&distances); part2(&distances); Ok(()) } fn part1(distances: &IndexMap<(usize, usize), isize>) { let mut out = 0; for ((node1, d1), (node2, d2)) in distances.iter().tuple_combinations() { let dist = (node1.0.abs_diff(node2.0) + node1.1.abs_diff(node2.1)) as isize; if dist == 2 && d2 - d1 - dist >= 100 { out += 1; } } eprintln!("{out}"); } fn part2(distances: &IndexMap<(usize, usize), isize>) { let mut out = 0; for ((node1, d1), (node2, d2)) in distances.iter().tuple_combinations() { let dist = (node1.0.abs_diff(node2.0) + node1.1.abs_diff(node2.1)) as isize; if dist <= 20 && d2 - d1 - dist >= 100 { out += 1; } } eprintln!("{out}"); } fn dists(input: &Input) -> IndexMap<(usize, usize), isize> { let mut queue = VecDeque::new(); let mut distances = IndexMap::new(); distances.insert(input.start, 0); queue.push_back((input.start, 0)); while let Some(((nx, ny), dist)) = queue.pop_front() { for next in [ (nx.wrapping_sub(1), ny), (nx + 1, ny), (nx, ny.wrapping_sub(1)), (nx, ny + 1), ] { if input.grid.contains(&next) && !distances.contains_key(&next) { distances.insert(next, dist + 1); queue.push_back((next, dist + 1)); } } } distances } #[derive(Default, Debug, Clone)] pub struct Input { start: (usize, usize), end: (usize, usize), grid: HashSet<(usize, usize)>, } fn parse_input(s: &str) -> Input { let mut input = Input::default(); for (y, s) in s.trim().lines().enumerate() { for (x, c) in s.chars().enumerate() { match c { '#' => continue, 'S' => input.start = (x, y), 'E' => input.end = (x, y), '.' => (), _ => panic!("invalid char in input: {c}"), } input.grid.insert((x, y)); } } input }