1use utils::prelude::*;
2use utils::str::TinyStr8;
3
4#[derive(Clone, Debug)]
6pub struct Day14 {
7 order: Vec<usize>,
8 reactions: Vec<Reaction>,
9}
10
11#[derive(Clone, Debug)]
12struct Reaction {
13 output_amount: u32,
14 inputs: Vec<Input>,
15}
16
17#[derive(Clone, Copy, Debug)]
18struct Input {
19 index: usize,
20 amount: u32,
21}
22
23const ORE: usize = 0;
24const FUEL: usize = 1;
25const PART2_LIMIT: u64 = 1_000_000_000_000;
26
27impl Day14 {
28 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
29 let component = parser::number_range(1..=u32::MAX)
30 .then(parser::tinystr8(u8::is_ascii_uppercase).with_prefix(b' '));
31 let reaction = component
32 .repeat(", ", 1)
33 .then(component.with_prefix(" => "))
34 .with_consumed()
35 .with_eol();
36
37 let mut names = vec![
38 const { TinyStr8::from_const(b"ORE") },
39 const { TinyStr8::from_const(b"FUEL") },
40 ];
41 let mut reactions = vec![
42 Some(Reaction {
43 output_amount: 1,
44 inputs: Vec::new(),
45 }),
46 None,
47 ];
48
49 for item in reaction.parse_iterator(input) {
50 let ((inputs, (output_amount, output_index)), line) = item?;
51
52 let mut intern = |name| {
54 if let Some(index) = names.iter().position(|&n| n == name) {
55 return index;
56 }
57 names.push(name);
58 reactions.push(None);
59 reactions.len() - 1
60 };
61
62 let output = intern(output_index);
63 let inputs = inputs
64 .into_iter()
65 .map(|(amount, name)| Input {
66 index: intern(name),
67 amount,
68 })
69 .collect();
70
71 if output == ORE {
72 return Err(InputError::new(
73 input,
74 line,
75 "expected ORE to be an input only",
76 ));
77 }
78 if reactions[output].is_some() {
79 return Err(InputError::new(
80 input,
81 line,
82 "duplicate reaction for chemical",
83 ));
84 }
85
86 reactions[output] = Some(Reaction {
87 output_amount,
88 inputs,
89 });
90 }
91
92 let Some(reactions): Option<Vec<Reaction>> = reactions.into_iter().collect() else {
93 return Err(InputError::new(
94 input,
95 0,
96 "expected every chemical to have a reaction",
97 ));
98 };
99
100 let Some(order) = Self::reaction_order(&reactions) else {
101 return Err(InputError::new(input, 0, "expected acyclic reaction graph"));
102 };
103
104 Ok(Self { order, reactions })
105 }
106
107 fn reaction_order(reactions: &[Reaction]) -> Option<Vec<usize>> {
108 #[derive(Clone, Copy, Debug)]
109 enum State {
110 Unvisited,
111 Visiting,
112 Done,
113 }
114
115 fn depth(
116 reactions: &[Reaction],
117 chemical: usize,
118 state: &mut [State],
119 depths: &mut [usize],
120 ) -> Option<usize> {
121 match state[chemical] {
122 State::Done => Some(depths[chemical]),
123 State::Visiting => None,
124 State::Unvisited if chemical == ORE => Some(depths[chemical]),
125 State::Unvisited => {
126 state[chemical] = State::Visiting;
127
128 let mut max_input_depth = 0;
129 for input in &reactions[chemical].inputs {
130 max_input_depth =
131 max_input_depth.max(depth(reactions, input.index, state, depths)?);
132 }
133 depths[chemical] = max_input_depth + 1;
134
135 state[chemical] = State::Done;
136 Some(depths[chemical])
137 }
138 }
139 }
140
141 let mut state = vec![State::Unvisited; reactions.len()];
142 let mut depths = vec![0; reactions.len()];
143 depth(reactions, FUEL, &mut state, &mut depths)?;
144
145 let mut order = (1..reactions.len())
146 .filter(|&i| matches!(state[i], State::Done))
147 .collect::<Vec<_>>();
148 order.sort_unstable_by_key(|&i| depths[i]);
149
150 Some(order)
151 }
152
153 #[must_use]
154 pub fn part1(&self) -> u64 {
155 self.ore_needed(1)
156 }
157
158 #[must_use]
159 pub fn part2(&self) -> u64 {
160 let mut lower = PART2_LIMIT / self.ore_needed(1);
163 let mut upper = 2 * lower.max(1);
164
165 while self.ore_needed(upper) <= PART2_LIMIT {
166 lower = upper;
167 upper *= 2;
168 }
169
170 while lower + 1 < upper {
171 let middle = (lower + upper) / 2;
172 if self.ore_needed(middle) <= PART2_LIMIT {
173 lower = middle;
174 } else {
175 upper = middle;
176 }
177 }
178
179 lower
180 }
181
182 #[inline]
183 fn ore_needed(&self, fuel_amount: u64) -> u64 {
184 let mut demand = vec![0u64; self.reactions.len()];
185 demand[FUEL] = fuel_amount;
186
187 for &chemical in self.order.iter().rev() {
188 let needed = demand[chemical];
189 let reaction = &self.reactions[chemical];
190 let batches = needed.div_ceil(u64::from(reaction.output_amount));
191 for input in &reaction.inputs {
192 demand[input.index] += u64::from(input.amount) * batches;
193 }
194 }
195
196 demand[ORE]
197 }
198}
199
200examples!(Day14 -> (u64, u64) [
201 {file: "day14_example0.txt", part1: 31},
202 {file: "day14_example1.txt", part1: 165},
203 {file: "day14_example2.txt", part1: 13312, part2: 82892753},
204 {file: "day14_example3.txt", part1: 180697, part2: 5586022},
205 {file: "day14_example4.txt", part1: 2210736, part2: 460664},
206]);