1use crate::util::bitset::*;
3use crate::util::math::*;
4use crate::util::parse::*;
5use std::array::from_fn;
6
7const MAX_BUTTONS: usize = 14;
8const MAX_JOLTAGES: usize = 11;
9
10type Column = [i32; MAX_JOLTAGES];
11
12pub struct Machine {
13 lights: u32,
14 buttons: Vec<u32>,
15 joltages: Vec<i32>,
16}
17
18struct Subspace {
19 rank: usize,
20 nullity: usize,
21 lcm: i32,
22 rhs: Column,
23 basis: Vec<Basis>,
24}
25
26#[derive(Clone, Copy)]
27struct Basis {
28 limit: i32,
29 cost: i32,
30 vs: Column,
31}
32
33pub fn parse(input: &str) -> Vec<Machine> {
34 input.lines().map(parse_machine).collect()
35}
36
37pub fn part1(input: &[Machine]) -> u32 {
38 input.iter().map(configure_lights).sum()
39}
40
41pub fn part2(input: &[Machine]) -> i32 {
42 input.iter().map(configure_joltages).sum()
43}
44
45fn parse_machine(line: &str) -> Machine {
47 let tokens: Vec<_> = line.split_ascii_whitespace().collect();
48 let last = tokens.len() - 1;
49
50 let lights = tokens[0][1..]
51 .bytes()
52 .enumerate()
53 .fold(0, |light, (i, b)| light | (u32::from(b == b'#') << i));
54 let buttons = tokens[1..last]
55 .iter()
56 .map(|token| token.iter_unsigned().fold(0, |button, i: u32| button | (1 << i)))
57 .collect();
58 let joltages = tokens[last].iter_signed().collect();
59
60 Machine { lights, buttons, joltages }
61}
62
63fn configure_lights(machine: &Machine) -> u32 {
66 let Machine { lights, buttons, joltages } = machine;
67 let width = joltages.len();
68 let height = buttons.len();
69
70 let mut rank = 0;
71 let mut h = [0; MAX_BUTTONS];
72 let mut u: [u32; MAX_BUTTONS] = from_fn(|row| 1 << row);
73
74 h[..height].copy_from_slice(buttons);
75
76 for col in 0..width {
77 let mask = 1 << col;
78 let Some(found) = (rank..height).find(|&row| h[row] & mask != 0) else {
79 continue;
80 };
81
82 h.swap(rank, found);
83 u.swap(rank, found);
84
85 for row in 0..height {
86 if row != rank && h[row] & mask != 0 {
87 h[row] ^= h[rank];
88 u[row] ^= u[rank];
89 }
90 }
91
92 rank += 1;
93 }
94
95 let nullity = height - rank;
96 let particular_solution = (0..rank).fold(0, |particular_solution, row| {
97 let mask = 1 << h[row].trailing_zeros();
98 particular_solution ^ if lights & mask == 0 { 0 } else { u[row] }
99 });
100
101 (0..1 << nullity)
102 .map(|i| {
103 i.biterator().fold(particular_solution, |presses, j| presses ^ u[rank + j]).count_ones()
104 })
105 .min()
106 .unwrap()
107}
108
109fn configure_joltages(machine: &Machine) -> i32 {
114 let subspace @ Subspace { rank, nullity, lcm, rhs, .. } = gaussian_elimination(machine);
115 let particular_solution = rhs[..rank].iter().sum();
116
117 if nullity == 0 {
118 particular_solution / lcm
119 } else {
120 let remaining = (1 << subspace.basis.len()) - 1;
121 recurse(&subspace, rhs, remaining, particular_solution).unwrap()
122 }
123}
124
125fn gaussian_elimination(machine: &Machine) -> Subspace {
126 let Machine { buttons, joltages, .. } = machine;
127 let width = buttons.len();
128 let height = joltages.len();
129
130 assert!(width < MAX_BUTTONS);
131 assert!(height < MAX_JOLTAGES);
132 let mut equations = [[0; MAX_BUTTONS]; MAX_JOLTAGES];
133
134 for row in 0..height {
135 equations[row][width] = joltages[row];
136 }
137
138 for col in 0..width {
139 let mut limit = i32::MAX;
140
141 for row in buttons[col].biterator() {
142 equations[row][col] = 1;
143 limit = limit.min(joltages[row]);
144 }
145
146 equations[height][col] = limit;
147 }
148
149 let mut rank = 0;
150 let mut last = width;
151
152 while rank < height && rank < last {
153 if let Some(found) = (rank..height)
154 .filter(|&row| equations[row][rank] != 0)
155 .min_by_key(|&row| equations[row][rank].abs())
156 {
157 equations.swap(rank, found);
158 let mut pivot = equations[rank][rank];
159
160 if pivot < 0 {
161 pivot *= -1;
162 equations[rank][rank..=width].iter_mut().for_each(|c| *c *= -1);
163 }
164
165 for row in 0..height {
166 let coefficient = equations[row][rank];
167 if row != rank && coefficient != 0 {
168 for col in 0..equations[row].len() {
169 equations[row][col] =
170 pivot * equations[row][col] - coefficient * equations[rank][col];
171 }
172 }
173 }
174
175 rank += 1;
176 } else {
177 last -= 1;
178 equations[..=height].iter_mut().for_each(|row| row.swap(rank, last));
179 }
180 }
181
182 let lcm = (0..rank).fold(1, |lcm, pivot| lcm.lcm(equations[pivot][pivot]));
183
184 for (pivot, equation) in equations[..rank].iter_mut().enumerate() {
185 let q = lcm / equation[pivot];
186 equation[rank..=width].iter_mut().for_each(|c| *c *= q);
187 }
188
189 let nullity = width - rank;
190 let rhs = from_fn(|row| equations[row][width]);
191 let basis: Vec<_> = (0..nullity)
192 .map(|col| {
193 let limit = equations[height][col + rank];
194 let vs = from_fn(|row| equations[row][rank + col]);
195 let cost = lcm - vs[..rank].iter().sum::<i32>();
196 Basis { limit, cost, vs }
197 })
198 .collect();
199
200 Subspace { rank, nullity, lcm, rhs, basis }
201}
202
203fn recurse(subspace: &Subspace, mut rhs: Column, remaining: u32, presses: i32) -> Option<i32> {
204 let rank = subspace.rank;
205 let mut temp = rhs;
206
207 for i in remaining.biterator() {
208 let free = &subspace.basis[i];
209 for (row, &v) in free.vs[..rank].iter().enumerate() {
210 if v < 0 {
211 temp[row] -= v * free.limit;
212 }
213 }
214 }
215
216 let mut min_value = i32::MAX;
217 let mut min_index = usize::MAX;
218 let mut global_lower = 0;
219 let mut global_upper = 0;
220
221 for i in remaining.biterator() {
222 let free = &subspace.basis[i];
223 let mut lower = 0;
224 let mut upper = free.limit;
225
226 for (&v, &rhs) in free.vs[..rank].iter().zip(&temp) {
227 if v > 0 {
228 upper = upper.min(rhs / v);
229 }
230 if v < 0 {
231 let rhs = rhs + v * free.limit;
232 lower = lower.max((rhs + v + 1) / v);
233 }
234 }
235
236 let size = upper - lower + 1;
237 if size > 0 && size < min_value {
238 min_value = size;
239 min_index = i;
240 global_lower = lower;
241 global_upper = upper;
242 }
243 }
244
245 if min_index == usize::MAX {
246 return None;
247 }
248
249 let remaining = remaining ^ (1 << min_index);
250 let lower = global_lower;
251 let upper = global_upper;
252 let Basis { cost, vs, .. } = &subspace.basis[min_index];
253 let cost = *cost;
254 let lcm = subspace.lcm;
255
256 if remaining != 0 {
257 rhs[..rank].iter_mut().zip(vs).for_each(|(rhs, v)| *rhs -= (lower - 1) * v);
258
259 (lower..upper + 1)
260 .filter_map(|n| {
261 rhs[..rank].iter_mut().zip(vs).for_each(|(rhs, v)| *rhs -= v);
262 recurse(subspace, rhs, remaining, presses + n * cost)
263 })
264 .min()
265 } else if cost >= 0 {
266 (lower..upper + 1).find_map(|n| {
267 let total = (presses + n * cost) / lcm;
268 rhs[..rank].iter().zip(vs).all(|(rhs, v)| (rhs - n * v) % lcm == 0).then_some(total)
269 })
270 } else {
271 (lower..upper + 1).rev().find_map(|n| {
272 let total = (presses + n * cost) / lcm;
273 rhs[..rank].iter().zip(vs).all(|(rhs, v)| (rhs - n * v) % lcm == 0).then_some(total)
274 })
275 }
276}