1use std::cmp::Reverse;
2use std::collections::BinaryHeap;
3use utils::array::ArrayVec;
4use utils::bit::BitIterator;
5use utils::grid;
6use utils::hash::FastMap;
7use utils::prelude::*;
8
9#[derive(Clone, Debug)]
15pub struct Day18 {
16 grid: Vec<u8>,
17 cols: usize,
18 starts: Vec<usize>,
19 key_positions: [Option<usize>; MAX_KEYS],
20 all_keys: u32,
21}
22
23#[derive(Clone, Copy, Debug, Default)]
24struct Edge {
25 distance: u32,
26 needed_mask: u32,
27 target_node_bit: u32,
28 target_key_bit: u32,
29}
30
31const MAX_KEYS: usize = 26;
32const MAX_ROBOTS: usize = 4;
33const MAX_NODES: usize = MAX_KEYS + MAX_ROBOTS;
34
35impl Day18 {
36 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
37 let mut starts = Vec::new();
38 let mut key_positions = [None; MAX_KEYS];
39 let mut all_keys = 0;
40 let mut all_doors = 0;
41 let (_, cols, grid) = grid::parse(
42 input,
43 1,
44 b'#',
45 |b| b,
46 |b| matches!(b, b'#' | b'.'),
47 |i, b| match b {
48 b'@' => {
49 starts.push(i);
50 Ok(b'@')
51 }
52 b'a'..=b'z' if key_positions[(b - b'a') as usize].is_some() => Err("duplicate key"),
53 b'a'..=b'z' => {
54 key_positions[(b - b'a') as usize] = Some(i);
55 all_keys |= 1 << (b - b'a');
56 Ok(b)
57 }
58 b'A'..=b'Z' => {
59 all_doors |= 1 << (b - b'A');
60 Ok(b)
61 }
62 _ => Err("expected '#', '.', '@' or letter"),
63 },
64 )?;
65 if grid.len() > usize::from(u16::MAX) {
66 return Err(InputError::new(input, 0, "grid too large"));
67 }
68 if starts.len() != 1 && starts.len() != 4 {
69 return Err(InputError::new(input, 0, "expected 1 or 4 entrances"));
70 }
71 if all_doors & !all_keys != 0 {
72 return Err(InputError::new(input, 0, "door without matching key"));
73 }
74
75 Ok(Self {
76 grid,
77 cols,
78 starts,
79 key_positions,
80 all_keys,
81 })
82 }
83
84 #[must_use]
85 pub fn part1(&self) -> u32 {
86 assert_eq!(self.starts.len(), 1, "part 1 requires a single entrance");
87 self.minimum_steps(&self.grid, &self.starts)
88 }
89
90 #[must_use]
91 pub fn part2(&self) -> u32 {
92 if self.starts.len() == 4 {
93 return self.minimum_steps(&self.grid, &self.starts);
94 }
95
96 let mut grid = self.grid.clone();
97 let middle = self.starts[0];
98 let cols = self.cols as isize;
99 for dy in [-cols, 0, cols] {
100 for dx in [-1, 0, 1] {
101 let i = middle.wrapping_add_signed(dy + dx);
102 if grid[i] != (if dy == 0 && dx == 0 { b'@' } else { b'.' }) {
103 panic!("expected empty spaces around the entrance");
104 }
105 grid[i] = b'#';
106 }
107 }
108
109 let starts = [
110 middle.wrapping_add_signed(-cols - 1),
111 middle.wrapping_add_signed(-cols + 1),
112 middle.wrapping_add_signed(cols - 1),
113 middle.wrapping_add_signed(cols + 1),
114 ];
115 for i in starts {
116 grid[i] = b'@';
117 }
118
119 self.minimum_steps(&grid, &starts)
120 }
121
122 fn minimum_steps(&self, grid: &[u8], starts: &[usize]) -> u32 {
123 let graph = self.build_key_graph(grid, starts);
124 let positions = (1u32 << starts.len()) - 1;
125 let mut cache = FastMap::with_capacity(8192);
126 let result = Self::search(&graph, positions, self.all_keys, &mut cache);
127 if result == u32::MAX {
128 panic!("no solution found");
129 }
130 result
131 }
132
133 fn build_key_graph(&self, grid: &[u8], starts: &[usize]) -> Vec<ArrayVec<Edge, MAX_KEYS>> {
134 let cols = self.cols;
135 let key_count = self.all_keys.count_ones() as usize;
136 let mut key_to_node = [u8::MAX; MAX_KEYS];
137 let mut key_nodes = Vec::with_capacity(key_count);
138 let mut node_positions = Vec::with_capacity(starts.len() + key_count);
139 let mut node_key_bits = Vec::with_capacity(starts.len() + key_count);
140 node_positions.extend_from_slice(starts);
141 node_key_bits.resize(starts.len(), 0);
142
143 for (key, position) in self.key_positions.iter().enumerate() {
144 if let Some(position) = *position {
145 let node = node_positions.len() as u8;
146 key_to_node[key] = node;
147 key_nodes.push((key, node));
148 node_positions.push(position);
149 node_key_bits.push(1 << key);
150 }
151 }
152
153 let mut grid_node_ids = vec![u32::MAX; grid.len()];
156 let mut grid_node_positions = Vec::new();
157 for index in 0..grid.len() {
158 if grid[index] == b'#' {
159 continue;
160 }
161
162 let mut degree = 0;
163 for next in [index - 1, index + 1, index - cols, index + cols] {
164 degree += u32::from(grid[next] != b'#');
165 }
166
167 if grid[index] != b'.' || degree != 2 {
168 grid_node_ids[index] = grid_node_positions.len() as u32;
169 grid_node_positions.push(index);
170 }
171 }
172
173 let mut grid_edges = vec![ArrayVec::<(u32, u16), 4>::new(); grid_node_positions.len()];
174 for (from, &index) in grid_node_positions.iter().enumerate() {
175 for mut next in [index - 1, index + 1, index - cols, index + cols] {
176 if grid[next] == b'#' {
177 continue;
178 }
179
180 let mut previous = index;
181 let mut edge_dist = 1u16;
182 while grid_node_ids[next] == u32::MAX {
183 let mut advance = next;
184 for candidate in [next - 1, next + 1, next - cols, next + cols] {
185 if candidate != previous && grid[candidate] != b'#' {
186 advance = candidate;
187 break;
188 }
189 }
190 previous = next;
191 next = advance;
192 edge_dist += 1;
193 }
194
195 grid_edges[from]
196 .push((grid_node_ids[next], edge_dist))
197 .expect("expected at most four edges per maze node");
198 }
199 }
200
201 let node_count = node_positions.len();
203 let mut dist_matrix = [[u16::MAX; MAX_NODES]; MAX_NODES];
204 let mut required_keys = [[0u32; MAX_NODES]; MAX_NODES];
205 let mut heap = BinaryHeap::with_capacity(grid_node_positions.len());
206 let mut best_dist = vec![u16::MAX; grid_node_positions.len()];
207 for (from, &start_index) in node_positions.iter().enumerate() {
208 best_dist.fill(u16::MAX);
209 heap.push(Reverse((0u16, grid_node_ids[start_index] as usize, 0u32)));
210
211 while let Some(Reverse((distance, grid_node, mut required_doors))) = heap.pop() {
212 if distance >= best_dist[grid_node] {
213 continue;
214 }
215 best_dist[grid_node] = distance;
216
217 let tile = grid[grid_node_positions[grid_node]];
218 if tile.is_ascii_uppercase() {
219 required_doors |= 1 << (tile - b'A');
220 }
221
222 if tile.is_ascii_lowercase() && distance != 0 {
223 let to = key_to_node[(tile - b'a') as usize] as usize;
224 dist_matrix[from][to] = distance;
225 dist_matrix[to][from] = distance;
226 required_keys[from][to] = required_doors;
227 required_keys[to][from] = required_doors;
228 continue;
229 }
230
231 for &(next, edge_dist) in &grid_edges[grid_node] {
232 let next = next as usize;
233 let next_distance = distance.saturating_add(edge_dist);
234 if next_distance < best_dist[next] {
235 heap.push(Reverse((next_distance, next, required_doors)));
236 }
237 }
238 }
239 }
240
241 for (i, row) in dist_matrix.iter_mut().enumerate().take(node_count) {
242 row[i] = 0;
243 }
244
245 for k in 0..node_count {
247 let via_key = node_key_bits[k];
248 for i in 0..node_count {
249 for j in 0..node_count {
250 let candidate = dist_matrix[i][k].saturating_add(dist_matrix[k][j]);
251 if dist_matrix[i][j] > candidate {
252 dist_matrix[i][j] = candidate;
253 required_keys[i][j] = required_keys[i][k] | via_key | required_keys[k][j];
254 }
255 }
256 }
257 }
258
259 let mut graph = Vec::with_capacity(node_count);
260 for from in 0..node_count {
261 let mut edges = ArrayVec::new();
262 for &(key, target_node) in &key_nodes {
263 if target_node as usize == from {
264 continue;
265 }
266
267 let distance = dist_matrix[from][target_node as usize];
268 if distance != u16::MAX {
269 edges
270 .push(Edge {
271 distance: u32::from(distance),
272 needed_mask: required_keys[from][target_node as usize] | (1 << key),
273 target_node_bit: 1u32 << target_node,
274 target_key_bit: 1 << key,
275 })
276 .expect("expected at most MAX_KEYS target key edges");
277 }
278 }
279 graph.push(edges);
280 }
281
282 graph
283 }
284
285 fn search(
286 graph: &[ArrayVec<Edge, MAX_KEYS>],
287 positions: u32,
288 remaining: u32,
289 cache: &mut FastMap<(u32, u32), u32>,
290 ) -> u32 {
291 if remaining == 0 {
292 return 0;
293 }
294
295 let key = (positions, remaining);
296 if let Some(&cached) = cache.get(&key) {
297 return cached;
298 }
299
300 let mut best = u32::MAX;
301 for (from, from_bit) in BitIterator::ones(positions) {
302 for &edge in &graph[from as usize] {
303 if remaining & edge.needed_mask == edge.target_key_bit {
306 let rest = Self::search(
307 graph,
308 positions ^ from_bit ^ edge.target_node_bit,
309 remaining ^ edge.target_key_bit,
310 cache,
311 );
312 if rest != u32::MAX {
313 best = best.min(edge.distance + rest);
314 }
315 }
316 }
317 }
318
319 cache.insert(key, best);
320 best
321 }
322}
323
324examples!(Day18 -> (u32, u32) [
325 {file: "day18_example0.txt", part1: 8},
326 {file: "day18_example1.txt", part1: 86},
327 {file: "day18_example2.txt", part1: 132},
328 {file: "day18_example3.txt", part1: 136},
329 {file: "day18_example4.txt", part1: 81},
330 {file: "day18_example5.txt", part2: 8},
331 {file: "day18_example6.txt", part2: 24},
332 {file: "day18_example7.txt", part2: 32},
333 {file: "day18_example8.txt", part2: 72},
334]);