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 assert!(
103 grid[i] == (if dy == 0 && dx == 0 { b'@' } else { b'.' }),
104 "expected empty spaces around the entrance"
105 );
106 grid[i] = b'#';
107 }
108 }
109
110 let starts = [
111 middle.wrapping_add_signed(-cols - 1),
112 middle.wrapping_add_signed(-cols + 1),
113 middle.wrapping_add_signed(cols - 1),
114 middle.wrapping_add_signed(cols + 1),
115 ];
116 for i in starts {
117 grid[i] = b'@';
118 }
119
120 self.minimum_steps(&grid, &starts)
121 }
122
123 fn minimum_steps(&self, grid: &[u8], starts: &[usize]) -> u32 {
124 let graph = self.build_key_graph(grid, starts);
125 let positions = (1u32 << starts.len()) - 1;
126 let mut cache = FastMap::with_capacity(8192);
127 let result = Self::search(&graph, positions, self.all_keys, &mut cache);
128 assert!(result != u32::MAX, "no solution found");
129 result
130 }
131
132 fn build_key_graph(&self, grid: &[u8], starts: &[usize]) -> Vec<ArrayVec<Edge, MAX_KEYS>> {
133 let cols = self.cols;
134 let key_count = self.all_keys.count_ones() as usize;
135 let mut key_to_node = [u8::MAX; MAX_KEYS];
136 let mut key_nodes = Vec::with_capacity(key_count);
137 let mut node_positions = Vec::with_capacity(starts.len() + key_count);
138 let mut node_key_bits = Vec::with_capacity(starts.len() + key_count);
139 node_positions.extend_from_slice(starts);
140 node_key_bits.resize(starts.len(), 0);
141
142 for (key, position) in self.key_positions.iter().enumerate() {
143 if let Some(position) = *position {
144 let node = node_positions.len() as u8;
145 key_to_node[key] = node;
146 key_nodes.push((key, node));
147 node_positions.push(position);
148 node_key_bits.push(1 << key);
149 }
150 }
151
152 let mut grid_node_ids = vec![u32::MAX; grid.len()];
155 let mut grid_node_positions = Vec::new();
156 for index in 0..grid.len() {
157 if grid[index] == b'#' {
158 continue;
159 }
160
161 let mut degree = 0;
162 for next in [index - 1, index + 1, index - cols, index + cols] {
163 degree += u32::from(grid[next] != b'#');
164 }
165
166 if grid[index] != b'.' || degree != 2 {
167 grid_node_ids[index] = grid_node_positions.len() as u32;
168 grid_node_positions.push(index);
169 }
170 }
171
172 let mut grid_edges = vec![ArrayVec::<(u32, u16), 4>::new(); grid_node_positions.len()];
173 for (from, &index) in grid_node_positions.iter().enumerate() {
174 for mut next in [index - 1, index + 1, index - cols, index + cols] {
175 if grid[next] == b'#' {
176 continue;
177 }
178
179 let mut previous = index;
180 let mut edge_dist = 1u16;
181 while grid_node_ids[next] == u32::MAX {
182 let mut advance = next;
183 for candidate in [next - 1, next + 1, next - cols, next + cols] {
184 if candidate != previous && grid[candidate] != b'#' {
185 advance = candidate;
186 break;
187 }
188 }
189 previous = next;
190 next = advance;
191 edge_dist += 1;
192 }
193
194 grid_edges[from]
195 .push((grid_node_ids[next], edge_dist))
196 .expect("expected at most four edges per maze node");
197 }
198 }
199
200 let node_count = node_positions.len();
202 let mut dist_matrix = [[u16::MAX; MAX_NODES]; MAX_NODES];
203 let mut required_keys = [[0u32; MAX_NODES]; MAX_NODES];
204 let mut heap = BinaryHeap::with_capacity(grid_node_positions.len());
205 let mut best_dist = vec![u16::MAX; grid_node_positions.len()];
206 for (from, &start_index) in node_positions.iter().enumerate() {
207 best_dist.fill(u16::MAX);
208 heap.push(Reverse((0u16, grid_node_ids[start_index] as usize, 0u32)));
209
210 while let Some(Reverse((distance, grid_node, mut required_doors))) = heap.pop() {
211 if distance >= best_dist[grid_node] {
212 continue;
213 }
214 best_dist[grid_node] = distance;
215
216 let tile = grid[grid_node_positions[grid_node]];
217 if tile.is_ascii_uppercase() {
218 required_doors |= 1 << (tile - b'A');
219 }
220
221 if tile.is_ascii_lowercase() && distance != 0 {
222 let to = key_to_node[(tile - b'a') as usize] as usize;
223 dist_matrix[from][to] = distance;
224 dist_matrix[to][from] = distance;
225 required_keys[from][to] = required_doors;
226 required_keys[to][from] = required_doors;
227 continue;
228 }
229
230 for &(next, edge_dist) in &grid_edges[grid_node] {
231 let next = next as usize;
232 let next_distance = distance.saturating_add(edge_dist);
233 if next_distance < best_dist[next] {
234 heap.push(Reverse((next_distance, next, required_doors)));
235 }
236 }
237 }
238 }
239
240 for (i, row) in dist_matrix.iter_mut().enumerate().take(node_count) {
241 row[i] = 0;
242 }
243
244 for k in 0..node_count {
246 let via_key = node_key_bits[k];
247 for i in 0..node_count {
248 for j in 0..node_count {
249 let candidate = dist_matrix[i][k].saturating_add(dist_matrix[k][j]);
250 if dist_matrix[i][j] > candidate {
251 dist_matrix[i][j] = candidate;
252 required_keys[i][j] = required_keys[i][k] | via_key | required_keys[k][j];
253 }
254 }
255 }
256 }
257
258 let mut graph = Vec::with_capacity(node_count);
259 for from in 0..node_count {
260 let mut edges = ArrayVec::new();
261 for &(key, target_node) in &key_nodes {
262 if target_node as usize == from {
263 continue;
264 }
265
266 let distance = dist_matrix[from][target_node as usize];
267 if distance != u16::MAX {
268 edges
269 .push(Edge {
270 distance: u32::from(distance),
271 needed_mask: required_keys[from][target_node as usize] | (1 << key),
272 target_node_bit: 1u32 << target_node,
273 target_key_bit: 1 << key,
274 })
275 .expect("expected at most MAX_KEYS target key edges");
276 }
277 }
278 graph.push(edges);
279 }
280
281 graph
282 }
283
284 fn search(
285 graph: &[ArrayVec<Edge, MAX_KEYS>],
286 positions: u32,
287 remaining: u32,
288 cache: &mut FastMap<(u32, u32), u32>,
289 ) -> u32 {
290 if remaining == 0 {
291 return 0;
292 }
293
294 let key = (positions, remaining);
295 if let Some(&cached) = cache.get(&key) {
296 return cached;
297 }
298
299 let mut best = u32::MAX;
300 for (from, from_bit) in BitIterator::ones(positions) {
301 for &edge in &graph[from as usize] {
302 if remaining & edge.needed_mask == edge.target_key_bit {
305 let rest = Self::search(
306 graph,
307 positions ^ from_bit ^ edge.target_node_bit,
308 remaining ^ edge.target_key_bit,
309 cache,
310 );
311 if rest != u32::MAX {
312 best = best.min(edge.distance + rest);
313 }
314 }
315 }
316 }
317
318 cache.insert(key, best);
319 best
320 }
321}
322
323examples!(Day18 -> (u32, u32) [
324 {file: "day18_example0.txt", part1: 8},
325 {file: "day18_example1.txt", part1: 86},
326 {file: "day18_example2.txt", part1: 132},
327 {file: "day18_example3.txt", part1: 136},
328 {file: "day18_example4.txt", part1: 81},
329 {file: "day18_example5.txt", part2: 8},
330 {file: "day18_example6.txt", part2: 24},
331 {file: "day18_example7.txt", part2: 32},
332 {file: "day18_example8.txt", part2: 72},
333]);