1use crate::util::bitset::*;
3use crate::util::parse::*;
4use crate::util::thread::*;
5use std::collections::BTreeSet;
6
7type Machine = (usize, Vec<usize>, Vec<i32>);
8type Input = (Vec<i32>, Vec<i32>);
9
10pub fn parse(input: &str) -> Input {
12 let items: Vec<_> = input.lines().collect();
13
14 let result: Vec<Vec<_>> = spawn_parallel_iterator(&items, |iter| {
15 iter.map(|line| {
16 let machine = parse_machine(line);
17 (configure_lights(&machine), configure_joltages(&machine))
18 })
19 .collect()
20 });
21
22 result.into_iter().flatten().unzip()
23}
24
25pub fn part1(input: &Input) -> i32 {
26 let (presses, _) = input;
27 presses.iter().sum()
28}
29
30pub fn part2(input: &Input) -> i32 {
31 let (_, presses) = input;
32 presses.iter().sum()
33}
34
35fn parse_machine(line: &str) -> Machine {
37 let tokens: Vec<_> = line.split_ascii_whitespace().collect();
38 let last = tokens.len() - 1;
39
40 let lights = tokens[0]
41 .bytes()
42 .skip(1)
43 .enumerate()
44 .fold(0, |light, (i, b)| light | (usize::from(b == b'#') << i));
45 let buttons = tokens[1..last]
46 .iter()
47 .map(|token| token.iter_unsigned::<usize>().fold(0, |button, i| button | (1 << i)))
48 .collect();
49 let joltages = tokens[last].iter_signed::<i32>().collect();
50
51 (lights, buttons, joltages)
52}
53
54fn configure_lights((lights, buttons, _): &Machine) -> i32 {
57 let limit = 1 << buttons.len();
58 let mut set = 0;
59
60 loop {
61 set += 1;
62 let mut n = (1 << set) - 1;
63
64 while n < limit {
65 if *lights == n.biterator().fold(0, |acc, i| acc ^ buttons[i]) {
66 return set;
67 }
68 n = next_same_bits(n);
69 }
70 }
71}
72
73fn next_same_bits(n: i32) -> i32 {
76 let smallest = n & -n;
77 let ripple = n + smallest;
78 let ones = n ^ ripple;
79 let next = (ones >> 2) / smallest;
80 ripple | next
81}
82
83fn configure_joltages((_, buttons, joltages): &Machine) -> i32 {
87 let width = buttons.len();
88 let height = joltages.len();
89 let mut equations = vec![vec![0; width + 1]; height];
90 let mut limit = vec![i32::MAX; width];
91 let mut previous: BTreeSet<_> = (0..width).collect();
92 let mut current: BTreeSet<_> = BTreeSet::new();
93
94 for (row, &joltage) in joltages.iter().enumerate() {
103 equations[row][width] = joltage;
104
105 for col in 0..width {
106 if buttons[col] & (1 << row) != 0 {
107 equations[row][col] = 1;
108 limit[col] = limit[col].min(joltage);
109 }
110 }
111 }
112
113 while previous != current {
121 previous = current;
122 current = (0..width).collect();
123
124 let mut pivot_row = 0;
125 let mut pivot_col = 0;
126
127 while pivot_row < height && pivot_col < width {
128 let Some(found) = (pivot_row..height).find(|&row| {
129 let coefficient = equations[row][pivot_col];
130 coefficient != 0 && equations[row].iter().all(|c| c % coefficient == 0)
131 }) else {
132 pivot_col += 1;
133 continue;
134 };
135
136 equations.swap(pivot_row, found);
137 let coefficient = equations[pivot_row][pivot_col];
138 equations[pivot_row].iter_mut().for_each(|c| *c /= coefficient);
139
140 for row in 0..height {
141 if row != pivot_row {
142 let coefficient = equations[row][pivot_col];
143 let [from, to] = equations.get_disjoint_mut([pivot_row, row]).unwrap();
144 from.iter().zip(to).for_each(|(f, t)| *t -= coefficient * f);
145 }
146 }
147
148 current.remove(&pivot_col);
149 pivot_row += 1;
150 pivot_col += 1;
151 }
152 }
153
154 if current.is_empty() {
155 return (0..height).map(|row| equations[row][width]).sum();
156 }
157
158 let free = current.len();
160 let fixed = width - current.len();
161 let presses = (0..fixed).map(|row| equations[row][width]).sum::<i32>();
162 let mut cost = vec![0; free];
163 let mut coefficients = vec![vec![0; height]; free];
164 let mut rhs = vec![vec![0; height]; free];
165 let mut ordered_limit: Vec<_> = vec![0; free];
166
167 for (to, &from) in current.iter().enumerate() {
168 cost[to] = 1 - (0..fixed).map(|row| equations[row][from]).sum::<i32>();
169 ordered_limit[to] = limit[from];
170
171 for row in 0..height {
172 coefficients[to][row] = equations[row][from];
173 }
174 }
175
176 for row in 0..height {
177 rhs[0][row] = equations[row][width];
178 }
179
180 recurse(&cost, &ordered_limit, &coefficients, &mut rhs, fixed, presses, 0).unwrap()
181}
182
183fn recurse(
184 cost: &[i32],
185 limit: &[i32],
186 coefficients: &[Vec<i32>],
187 rhs: &mut [Vec<i32>],
188 fixed: usize,
189 presses: i32,
190 depth: usize,
191) -> Option<i32> {
192 let height = rhs[depth].len();
193
194 if depth == coefficients.len() - 1 {
195 let mut lower = 0;
199 let mut upper = limit[depth];
200
201 for (&coef, &rhs) in coefficients[depth].iter().zip(&rhs[depth]) {
203 if rhs >= 0 {
204 if coef > 0 {
205 upper = upper.min(rhs / coef);
206 }
207 } else if coef < 0 {
208 let floor = (rhs + coef + 1) / coef;
209 lower = lower.max(floor);
210 } else {
211 upper = -1;
212 }
213 }
214
215 for row in fixed..height {
217 let c = coefficients[depth][row];
218 let r = rhs[depth][row];
219
220 if c != 0 {
221 if r % c == 0 {
222 upper = upper.min(r / c);
223 lower = lower.max(r / c);
224 } else {
225 upper = -1;
226 }
227 }
228 }
229
230 let presses = presses + cost[depth] * if cost[depth] >= 0 { lower } else { upper };
231 (lower <= upper).then_some(presses)
232 } else {
233 (0..=limit[depth])
234 .filter_map(|x| {
235 let next_presses = presses + x * cost[depth];
236
237 for row in 0..height {
238 rhs[depth + 1][row] = rhs[depth][row] - x * coefficients[depth][row];
239 }
240
241 recurse(cost, limit, coefficients, rhs, fixed, next_presses, depth + 1)
242 })
243 .min()
244 }
245}