1use std::cmp::Reverse;
2use std::collections::BinaryHeap;
3use utils::grid;
4use utils::prelude::*;
5
6#[derive(Clone, Debug)]
8pub struct Day16 {
9 grid: Vec<u8>,
10 start: usize,
11 end: usize,
12 offsets: [isize; 4],
13 cheapest: Vec<[u32; 4]>,
14 turned: Vec<u8>,
15 part1: u32,
16}
17
18impl Day16 {
19 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
20 let (rows, cols, mut grid) = grid::from_str(input, |b| match b {
21 b'.' | b'#' | b'S' | b'E' => Some(b),
22 _ => None,
23 })?;
24
25 if !grid::is_enclosed(rows, cols, &grid, |&b| b == b'#') {
26 return Err(InputError::new(
27 input,
28 0,
29 "expected grid to be enclosed by walls",
30 ));
31 }
32
33 let mut starts = grid.iter().enumerate().filter(|&(_, &b)| b == b'S');
34 let Some((start, _)) = starts.next() else {
35 return Err(InputError::new(input, 0, "expected one start"));
36 };
37 if starts.count() > 0 {
38 return Err(InputError::new(input, 0, "expected one start"));
39 }
40 grid[start] = b'.';
41
42 let mut ends = grid.iter().enumerate().filter(|&(_, &b)| b == b'E');
43 let Some((end, _)) = ends.next() else {
44 return Err(InputError::new(input, 0, "expected one end"));
45 };
46 if ends.count() > 0 {
47 return Err(InputError::new(input, 0, "expected one end"));
48 }
49 grid[end] = b'.';
50
51 let mut instance = Self {
52 cheapest: vec![[u32::MAX; 4]; grid.len()],
53 turned: vec![0; grid.len()],
54 part1: 0,
55 grid,
56 start,
57 end,
58 offsets: [1, cols as isize, -1, -(cols as isize)],
59 };
60
61 if !instance.dijkstra() {
63 return Err(InputError::new(input, 'E', "no path"));
64 }
65
66 Ok(instance)
67 }
68
69 fn dijkstra(&mut self) -> bool {
70 let mut queue = BinaryHeap::new();
71 queue.push(Reverse((0, self.start, 0)));
72
73 while let Some(Reverse((score, index, dir))) = queue.pop() {
74 if score > self.cheapest[index][dir] {
75 continue;
76 }
77 if index == self.end {
78 self.part1 = score;
79 return true;
80 }
81
82 if let Some(branch) =
85 self.find_branch(index.wrapping_add_signed(self.offsets[dir]), dir, score + 1)
86 {
87 queue.push(Reverse(branch));
89 }
90
91 for next_dir in [(dir + 1) % 4, (dir + 3) % 4] {
92 if score + 1000 < self.cheapest[index][next_dir] {
94 self.cheapest[index][next_dir] = score + 1000;
95 self.turned[index] |= 1 << next_dir;
98
99 if let Some(branch) = self.find_branch(
100 index.wrapping_add_signed(self.offsets[next_dir]),
101 next_dir,
102 score + 1001,
103 ) {
104 queue.push(Reverse(branch));
105 }
106 }
107 }
108 }
109
110 false
111 }
112
113 fn find_branch(
114 &mut self,
115 mut index: usize,
116 mut dir: usize,
117 mut score: u32,
118 ) -> Option<(u32, usize, usize)> {
119 if self.grid[index] != b'.' {
120 return None;
121 }
122
123 while index != self.end {
124 let mut count = 0;
125 let mut next_index = 0;
126 let mut next_dir = 0;
127 for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
128 let i = index.wrapping_add_signed(self.offsets[d]);
129 if self.grid[i] == b'.' {
130 count += 1;
131 next_index = i;
132 next_dir = d;
133 }
134 }
135
136 if count == 0 {
137 return None;
138 } else if count > 1 {
139 break;
140 }
141
142 score += if dir == next_dir { 1 } else { 1001 };
143 index = next_index;
144 dir = next_dir;
145 }
146
147 if score <= self.cheapest[index][dir] {
148 self.cheapest[index][dir] = score;
149 self.turned[index] &= !(1 << dir);
150 Some((score, index, dir))
151 } else if score == self.cheapest[index][dir] {
152 self.turned[index] &= !(1 << dir);
155 None
156 } else {
157 None
158 }
159 }
160
161 #[must_use]
162 pub fn part1(&self) -> u32 {
163 self.part1
164 }
165
166 #[must_use]
167 pub fn part2(&self) -> u32 {
168 let mut on_best = vec![false; self.grid.len()];
169 on_best[self.start] = true;
170 on_best[self.end] = true;
171 for d in 0..4 {
172 if self.cheapest[self.end][d] == self.part1 {
173 let prev = self.end.wrapping_add_signed(-self.offsets[d]);
174 self.reverse(prev, d, self.part1 - 1, &mut on_best);
175 }
176 }
177 on_best.iter().filter(|&&b| b).count() as u32
178 }
179
180 fn reverse(&self, index: usize, dir: usize, score: u32, on_best: &mut [bool]) {
181 if on_best[index] {
182 return;
183 }
184 on_best[index] = true;
185
186 let mut count = 0;
187 let mut next_index = 0;
188 let mut next_dir = 0;
189 for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
190 let i = index.wrapping_add_signed(-self.offsets[d]);
191 if self.grid[i] == b'.' {
192 count += 1;
193 next_index = i;
194 next_dir = d;
195 }
196 }
197 assert!(count > 0);
198
199 if count == 1 {
200 let next_score = score - if dir == next_dir { 1 } else { 1001 };
201 self.reverse(next_index, next_dir, next_score, on_best);
202 } else {
203 for (next_dir, next_score) in [
205 (dir, score),
206 ((dir + 1) % 4, score - 1000),
207 ((dir + 3) % 4, score - 1000),
208 ] {
209 if self.cheapest[index][next_dir] == next_score
210 && self.turned[index] & (1 << next_dir) == 0
211 {
212 self.reverse(
213 index.wrapping_add_signed(-self.offsets[next_dir]),
214 next_dir,
215 next_score - 1,
216 on_best,
217 );
218 }
219 }
220 }
221 }
222}
223
224examples!(Day16 -> (u32, u32) [
225 {file: "day16_example0.txt", part1: 7036, part2: 45},
226 {file: "day16_example1.txt", part1: 11048, part2: 64},
227]);