1use std::collections::{HashMap, HashSet, VecDeque};
2use utils::bit::BitIterator;
3use utils::prelude::*;
4
5#[derive(Clone, Debug)]
13pub struct Day11 {
14 floors: [Floor; 4],
15 types: usize,
16}
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
18struct State {
19 floors: [Floor; 4],
20 elevator: u8,
21 steps: u16,
22}
23
24#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
25struct Floor {
26 generators: u8,
27 microchips: u8,
28}
29
30impl Day11 {
31 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
32 if input.lines().count() != 4 {
33 return Err(InputError::new(input, 0, "expected 4 floors"));
34 }
35
36 let mut floors = [Floor::default(); 4];
37 let mut types = HashMap::new();
38 for (
39 line,
40 Floor {
41 generators,
42 microchips,
43 },
44 ) in input.lines().zip(&mut floors)
45 {
46 for (matches, field) in [
47 (line.match_indices(" generator"), generators),
48 (line.match_indices("-compatible microchip"), microchips),
49 ] {
50 for (index, _) in matches {
51 let Some((_, type_str)) = line[..index].rsplit_once(' ') else {
52 return Err(InputError::new(input, index, "expected type"));
53 };
54
55 let types_len = types.len();
56 let type_index = *types.entry(type_str).or_insert(types_len);
57 if type_index >= 5 {
58 return Err(InputError::new(input, index, "too many types"));
59 }
60
61 *field |= 1 << type_index;
62 }
63 }
64 }
65
66 Ok(Self {
67 floors,
68 types: types.len(),
69 })
70 }
71
72 #[must_use]
73 pub fn part1(&self) -> u16 {
74 Self::minimum_steps(self.floors, self.types)
75 }
76
77 #[must_use]
78 pub fn part2(&self) -> u16 {
79 let mut floors = self.floors;
80 floors[0].generators |= 0b11 << self.types;
81 floors[0].microchips |= 0b11 << self.types;
82 Self::minimum_steps(floors, self.types + 2)
83 }
84
85 fn minimum_steps(floors: [Floor; 4], types: usize) -> u16 {
86 if types > 7 {
88 panic!("only 7 types supported"); }
90 for f in floors {
91 if f.generators != 0 && (f.microchips & !f.generators) != 0 {
92 panic!("invalid start state");
95 }
96 }
97
98 let mut queue = VecDeque::with_capacity(1024);
99 let mut visited = HashSet::with_capacity(10240);
100
101 let start = State {
102 floors,
103 elevator: 0,
104 steps: 0,
105 };
106 queue.push_back(start);
107 visited.insert(start.to_unique());
108
109 let all_types = !(u8::MAX << types);
110 while let Some(state) = queue.pop_front() {
111 if state.floors[3].microchips == all_types && state.floors[3].generators == all_types {
112 return state.steps;
113 }
114
115 let src = state.floors[state.elevator as usize];
116 let src_pairs = src.generators & src.microchips;
117 let src_unpaired_generators = src.generators & !src.microchips;
118
119 let (src_gen_can_move, src_gen_must_move) = if src.generators == 0 {
122 (0, 0)
124 } else if src_pairs.count_ones() > 2
125 || (src_pairs.count_ones() == 2 && src_unpaired_generators != 0)
126 || (src_pairs.count_ones() == 1 && src_unpaired_generators.count_ones() >= 2)
127 {
128 (src_unpaired_generators, 0)
131 } else if src_pairs.count_ones() == 2 && src_unpaired_generators == 0 {
132 (src_pairs, src_pairs)
135 } else if src_pairs.count_ones() == 1 && src_unpaired_generators.count_ones() <= 1 {
136 (src.generators, src_unpaired_generators)
138 } else {
139 (src.generators, 0)
141 };
142
143 for elevator in [state.elevator + 1, state.elevator.saturating_sub(1)] {
144 if elevator == state.elevator || elevator >= 4 {
145 continue;
146 }
147
148 if elevator < state.elevator
150 && ((elevator == 0 && state.floors[0].empty())
151 || (elevator == 1 && state.floors[0].empty() && state.floors[1].empty()))
152 {
153 continue;
154 }
155
156 let mut try_move = |generators: u8, microchips: u8| {
157 let mut next_state = state;
158 next_state.floors[state.elevator as usize].generators &= !generators;
159 next_state.floors[state.elevator as usize].microchips &= !microchips;
160
161 next_state.floors[elevator as usize].generators |= generators;
162 next_state.floors[elevator as usize].microchips |= microchips;
163
164 next_state.elevator = elevator;
165 next_state.steps += 1;
166
167 if visited.insert(next_state.to_unique()) {
168 queue.push_back(next_state);
169 }
170 };
171
172 let dst = state.floors[elevator as usize];
173 let dst_unpaired_microchips = dst.microchips & !dst.generators;
174
175 if src_pairs != 0 && dst_unpaired_microchips == 0 {
177 let pair = 1 << src_pairs.trailing_zeros();
178 try_move(pair, pair);
179 }
180
181 if src_gen_can_move != 0 {
183 let can_move = src_gen_can_move;
184 let mut must_move = src_gen_must_move;
185 if dst_unpaired_microchips != 0 {
186 must_move |= dst_unpaired_microchips;
188 }
189
190 if must_move.count_ones() <= 2 && (must_move & !can_move) == 0 {
191 if elevator > state.elevator {
192 if must_move.count_ones() == 2
194 || (must_move.count_ones() == 1 && (can_move & !must_move) == 0)
195 {
196 try_move(must_move, 0);
198 } else if must_move.count_ones() == 1 {
199 for (_, g1) in BitIterator::ones(can_move & !must_move) {
201 try_move(must_move | g1, 0);
202 }
203 } else if can_move.count_ones() == 1 {
204 try_move(can_move, 0);
206 } else {
207 for (_, g1) in BitIterator::ones(can_move) {
209 for (_, g2) in BitIterator::ones(can_move & (g1 - 1)) {
210 try_move(g1 | g2, 0);
211 }
212 }
213 }
214 } else {
215 if must_move != 0 {
217 try_move(must_move, 0);
219 } else {
220 for (_, g1) in BitIterator::ones(can_move) {
222 try_move(g1, 0);
223 }
224 }
225 }
226 }
227 }
228
229 if src.microchips != 0 {
231 let mut to_move = src.microchips;
232 if dst.generators != 0 {
233 to_move &= dst.generators;
235 }
236
237 if to_move != 0 {
238 if elevator > state.elevator {
239 if to_move.count_ones() >= 2 {
241 for (_, m1) in BitIterator::ones(to_move) {
243 for (_, m2) in BitIterator::ones(to_move & (m1 - 1)) {
244 try_move(0, m1 | m2);
245 }
246 }
247 } else {
248 try_move(0, to_move);
250 }
251 } else {
252 for (_, m1) in BitIterator::ones(to_move) {
254 try_move(0, m1);
255 }
256 }
257 }
258 }
259 }
260 }
261
262 panic!("no solution found")
263 }
264}
265
266impl State {
267 #[inline]
268 fn to_unique(self) -> u64 {
269 let mut output = [0u8; 8];
274 output[0] = self.elevator;
275
276 for (i, out) in output[1..].iter_mut().enumerate() {
277 for (f, floor) in self.floors.iter().enumerate() {
278 *out |= if floor.generators & (1 << i) != 0 {
279 1 << f
280 } else {
281 0
282 };
283 *out |= if floor.microchips & (1 << i) != 0 {
284 1 << (f + 4)
285 } else {
286 0
287 };
288 }
289 }
290 output[1..].sort_unstable();
291
292 u64::from_ne_bytes(output)
295 }
296}
297
298impl Floor {
299 fn empty(self) -> bool {
300 self.generators == 0 && self.microchips == 0
301 }
302}
303
304examples!(Day11 -> (u16, u16) [
305 {
306 input: "The first floor contains a hydrogen-compatible microchip and a lithium-compatible microchip.\n\
307 The second floor contains a hydrogen generator.\n\
308 The third floor contains a lithium generator.\n\
309 The fourth floor contains nothing relevant.",
310 part1: 11
311 },
312]);