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 part1: u32,
15}
16
17impl Day16 {
18 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
19 let ((_, cols, grid), start, end) = grid::parse_maze(input, 1)?;
20
21 let mut instance = Self {
22 cheapest: vec![[u32::MAX; 4]; grid.len()],
23 part1: 0,
24 grid,
25 start,
26 end,
27 offsets: [1, cols as isize, -1, -(cols as isize)],
28 };
29
30 if !instance.dijkstra() {
32 return Err(InputError::new(input, 'E', "no path"));
33 }
34
35 Ok(instance)
36 }
37
38 fn dijkstra(&mut self) -> bool {
39 let mut queue = BinaryHeap::new();
40 queue.push(Reverse((0, self.start, 0)));
41 self.cheapest[self.start][0] = 0;
42
43 while let Some(Reverse((score, index, dir))) = queue.pop() {
44 if score > self.cheapest[index][dir] {
45 continue;
46 }
47 if index == self.end {
48 self.part1 = score;
49 return true;
50 }
51
52 if let Some(branch) =
55 self.find_branch(index.wrapping_add_signed(self.offsets[dir]), dir, score + 1)
56 {
57 queue.push(Reverse(branch));
59 }
60
61 for next_dir in [(dir + 1) % 4, (dir + 3) % 4] {
62 if score + 1000 < self.cheapest[index][next_dir] {
64 self.cheapest[index][next_dir] = score + 1000;
65
66 if let Some(branch) = self.find_branch(
67 index.wrapping_add_signed(self.offsets[next_dir]),
68 next_dir,
69 score + 1001,
70 ) {
71 queue.push(Reverse(branch));
72 }
73 }
74 }
75 }
76
77 false
78 }
79
80 fn find_branch(
81 &mut self,
82 mut index: usize,
83 mut dir: usize,
84 mut score: u32,
85 ) -> Option<(u32, usize, usize)> {
86 if self.grid[index] != b'.' {
87 return None;
88 }
89
90 loop {
91 if score < self.cheapest[index][dir] {
92 self.cheapest[index][dir] = score;
93 } else if score > self.cheapest[index][dir] {
94 return None;
95 }
96
97 if index == self.end {
98 break;
99 }
100
101 let mut count = 0;
102 let mut next_index = 0;
103 let mut next_dir = 0;
104 for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
105 let i = index.wrapping_add_signed(self.offsets[d]);
106 if self.grid[i] == b'.' {
107 count += 1;
108 next_index = i;
109 next_dir = d;
110 }
111 }
112
113 if count == 0 {
114 return None;
115 } else if count > 1 {
116 break;
117 }
118
119 score += if dir == next_dir { 1 } else { 1001 };
120 index = next_index;
121 dir = next_dir;
122 }
123
124 Some((score, index, dir))
125 }
126
127 #[must_use]
128 pub fn part1(&self) -> u32 {
129 self.part1
130 }
131
132 #[must_use]
133 pub fn part2(&self) -> u32 {
134 let mut on_best = vec![0u8; self.grid.len()];
135 on_best[self.start] = 0b1111;
136 on_best[self.end] = 0b1111;
137 for d in 0..4 {
138 if self.cheapest[self.end][d] == self.part1 {
139 let prev = self.end.wrapping_add_signed(-self.offsets[d]);
140 self.reverse(prev, d, self.part1 - 1, &mut on_best);
141 }
142 }
143 on_best.iter().filter(|&&b| b != 0).count() as u32
144 }
145
146 fn reverse(&self, index: usize, dir: usize, score: u32, on_best: &mut [u8]) {
147 if on_best[index] & (1 << dir) != 0 {
148 return;
149 }
150 on_best[index] |= 1 << dir;
151
152 let mut count = 0;
153 let mut next_index = 0;
154 let mut next_dir = 0;
155 for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
156 let i = index.wrapping_add_signed(-self.offsets[d]);
157 if self.grid[i] == b'.' {
158 count += 1;
159 next_index = i;
160 next_dir = d;
161 }
162 }
163 assert!(count > 0);
164
165 if count == 1 {
166 let next_score = score - if dir == next_dir { 1 } else { 1001 };
167 self.reverse(next_index, next_dir, next_score, on_best);
168 } else {
169 for (next_dir, next_score) in [
171 (dir, score),
172 ((dir + 1) % 4, score - 1000),
173 ((dir + 3) % 4, score - 1000),
174 ] {
175 let next_index = index.wrapping_add_signed(-self.offsets[next_dir]);
176 if self.cheapest[index][next_dir] == next_score
177 && self.cheapest[next_index][next_dir] == next_score - 1
178 {
179 self.reverse(next_index, next_dir, next_score - 1, on_best);
180 }
181 }
182 }
183 }
184}
185
186examples!(Day16 -> (u32, u32) [
187 {file: "day16_example0.txt", part1: 7036, part2: 45},
188 {file: "day16_example1.txt", part1: 11048, part2: 64},
189]);