1use crate::util::grid::*;
2use crate::util::hash::*;
3use crate::util::point::*;
4use std::collections::VecDeque;
5
6pub struct Input {
7 extra: u32,
8 horizontal: [[u32; 6]; 6],
9 vertical: [[u32; 6]; 6],
10}
11
12struct State {
13 letter: u8,
14 skipped: bool,
15 grid: [[u8; 6]; 7],
16 convert: [u8; 32],
17 result: u32,
18}
19
20pub fn parse(input: &str) -> Input {
21 let mut grid = Grid::parse(input);
22 let width = grid.width;
23 let height = grid.height;
24
25 grid[Point::new(1, 0)] = b'#';
27 grid[Point::new(width - 2, height - 1)] = b'#';
28
29 let start = Point::new(1, 1);
31 let end = Point::new(width - 2, height - 2);
32
33 grid[start] = b'P';
35 grid[end] = b'P';
36
37 let mut poi = Vec::new();
38 poi.push(start);
39 poi.push(end);
40
41 for y in 1..height - 1 {
42 for x in 1..width - 1 {
43 let position = Point::new(x, y);
44
45 if grid[position] != b'#' {
46 let neighbors =
47 ORTHOGONAL.iter().map(|&o| position + o).filter(|&n| grid[n] != b'#').count();
48 if neighbors > 2 {
49 grid[position] = b'P';
50 poi.push(position);
51 }
52 }
53 }
54 }
55
56 let mut todo = VecDeque::new();
58 let mut edges = FastMap::new();
59 let mut weight = FastMap::new();
60
61 for from in poi {
62 todo.push_back((from, 0));
63 grid[from] = b'#';
64 weight.insert((from, from), 0);
65
66 while let Some((position, cost)) = todo.pop_front() {
67 for direction in ORTHOGONAL {
68 let to = position + direction;
69
70 match grid[to] {
71 b'#' => (),
72 b'P' => {
73 edges.entry(from).or_insert(FastSet::new()).insert(to);
74 edges.entry(to).or_insert(FastSet::new()).insert(from);
75 weight.insert((from, to), cost + 1);
76 weight.insert((to, from), cost + 1);
77 }
78 _ => {
79 todo.push_back((to, cost + 1));
80 grid[to] = b'#';
81 }
82 }
83 }
84 }
85 }
86
87 graph_to_grid(start, end, &edges, &weight)
89}
90
91pub fn part1(input: &Input) -> u32 {
94 let mut total = [[0; 6]; 6];
95
96 for y in 0..6 {
97 for x in 0..6 {
98 let left = if x == 0 { 0 } else { total[y][x - 1] + input.horizontal[y][x - 1] };
99 let above = if y == 0 { 0 } else { total[y - 1][x] + input.vertical[y - 1][x] };
100 total[y][x] = left.max(above);
101 }
102 }
103
104 input.extra + total[5][5]
105}
106
107pub fn part2(input: &Input) -> u32 {
109 let mut state =
110 State { letter: 2, skipped: false, grid: [[0; 6]; 7], convert: [0; 32], result: 0 };
111
112 state.grid[0][0] = 1;
113
114 for i in 0..32 {
115 state.convert[i] = i as u8;
116 }
117
118 dfs(input, &mut state, 0, 0, 0);
119 input.extra + state.result
120}
121
122#[expect(clippy::needless_range_loop)]
123fn graph_to_grid(
124 start: Point,
125 end: Point,
126 edges: &FastMap<Point, FastSet<Point>>,
127 weight: &FastMap<(Point, Point), u32>,
128) -> Input {
129 let mut extra = 2;
130 extra += edges[&start].iter().map(|&e| weight[&(start, e)]).sum::<u32>();
131 extra += edges[&end].iter().map(|&e| weight[&(e, end)]).sum::<u32>();
132
133 let mut places = [[ORIGIN; 6]; 6];
134 let mut horizontal = [[0; 6]; 6];
135 let mut vertical = [[0; 6]; 6];
136
137 let mut point = *edges[&start].iter().next().unwrap();
138 let mut seen = FastSet::new();
139 let mut next_perimeter = |point: Point| {
140 seen.insert(point);
141 *edges[&point]
142 .iter()
143 .find(|&next| edges[next].len() == 3 && !seen.contains(next))
144 .unwrap_or(&ORIGIN)
145 };
146
147 for y in 0..5 {
148 places[y][0] = point;
149 point = next_perimeter(point);
150 }
151
152 for x in 1..6 {
153 places[5][x] = point;
154 point = next_perimeter(point);
155 }
156
157 for y in (1..5).rev() {
158 places[y][5] = point;
159 point = next_perimeter(point);
160 }
161
162 for x in (1..5).rev() {
163 places[0][x] = point;
164 point = next_perimeter(point);
165 }
166
167 for y in 1..5 {
168 for x in 1..5 {
169 let above = places[y - 1][x];
170 let left = places[y][x - 1];
171 let (&point, _) = edges
172 .iter()
173 .find(|(k, v)| !seen.contains(k) && v.contains(&above) && v.contains(&left))
174 .unwrap();
175
176 places[y][x] = point;
177 seen.insert(point);
178 }
179 }
180
181 places[0][5] = places[0][4];
182 places[5][0] = places[5][1];
183
184 for y in 0..6 {
185 for x in 0..5 {
186 let key = (places[y][x], places[y][x + 1]);
187 horizontal[y][x] = *weight.get(&key).unwrap_or(&0);
188 }
189 }
190
191 for y in 0..5 {
192 for x in 0..6 {
193 let key = (places[y][x], places[y + 1][x]);
194 vertical[y][x] = *weight.get(&key).unwrap_or(&0);
195 }
196 }
197
198 Input { extra, horizontal, vertical }
199}
200
201fn dfs(input: &Input, state: &mut State, mut row: usize, mut col: usize, mut steps: u32) {
209 if col == 6 {
211 if row == 5 {
213 state.result = state.result.max(steps);
214 return;
215 }
216 row += 1;
217 col = 0;
218 }
219
220 if state.grid[row][col] == 0 {
221 if !(state.skipped || (row == 5 && col == 5)) {
223 state.skipped = true;
224 state.grid[row + 1][col] = 0;
225 dfs(input, state, row, col + 1, steps);
226 state.skipped = false;
227 }
228
229 if row < 5 {
231 let id = state.letter;
232 steps += input.vertical[row][col];
233
234 for end in (col + 1)..6 {
235 state.grid[row + 1][end - 1] = 0;
236 steps += input.horizontal[row][end - 1];
237
238 if state.grid[row][end] == 0 {
239 state.grid[row + 1][col] = id;
240 state.grid[row + 1][end] = id;
241 let extra = input.vertical[row][end];
242 state.letter += 1;
243 dfs(input, state, row, end + 1, steps + extra);
244 state.letter -= 1;
245 } else {
246 state.grid[row + 1][col] = state.convert[state.grid[row][end] as usize];
247 state.grid[row + 1][end] = 0;
248 dfs(input, state, row, end + 1, steps);
249 break;
250 }
251 }
252 }
253 } else {
254 let index = state.grid[row][col] as usize;
255 let id = state.convert[index];
256
257 if row < 5 || col == 5 {
259 state.grid[row + 1][col] = id;
260 let extra = input.vertical[row][col];
261 dfs(input, state, row, col + 1, steps + extra);
262 }
263
264 for end in (col + 1)..6 {
265 state.grid[row + 1][end - 1] = 0;
266 steps += input.horizontal[row][end - 1];
267
268 if state.grid[row][end] == 0 {
269 if row < 5 || end == 5 {
271 state.grid[row + 1][end] = id;
272 let extra = input.vertical[row][end];
273 dfs(input, state, row, end + 1, steps + extra);
274 }
275 } else {
276 let other = state.convert[state.grid[row][end] as usize];
279
280 if id != other {
281 state.grid[row + 1][end] = 0;
282 state.convert[index] = other;
283 dfs(input, state, row, end + 1, steps);
284 state.convert[index] = id;
285 }
286
287 break;
288 }
289 }
290 }
291}