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