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