-
Notifications
You must be signed in to change notification settings - Fork 15
/
day16.rs
117 lines (100 loc) · 3.88 KB
/
day16.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! # Reindeer Maze
//!
//! Solves part one and part two simultaneously.
//!
//! Part one is a normal [Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
//! search from start to end.
//!
//! Part two is a a BFS *backwards* from the end to the finish, tracing the cost exactly
//! to find all possible paths. This reuses the cost information from the Dijkstra without
//! requiring any extra state keeping for the paths.
use crate::util::grid::*;
use crate::util::point::*;
use std::collections::VecDeque;
type Input = (u32, usize);
/// Clockwise order starting with facing right.
const DIRECTIONS: [Point; 4] = [RIGHT, DOWN, LEFT, UP];
pub fn parse(input: &str) -> Input {
let grid = Grid::parse(input);
let start = grid.find(b'S').unwrap();
let end = grid.find(b'E').unwrap();
// Forwards Dijkstra. Since turns are so much more expensive than moving forward, we can
// treat this as a glorified BFS using two priority queues. This is much faster than using
// an actual min heap.
let mut todo_first = VecDeque::new();
let mut todo_second = VecDeque::new();
// State is `(position, direction)`.
let mut seen = grid.same_size_with([u32::MAX; 4]);
let mut lowest = u32::MAX;
todo_first.push_back((start, 0, 0));
seen[start][0] = 0;
while !todo_first.is_empty() {
while let Some((position, direction, cost)) = todo_first.pop_front() {
if cost >= lowest {
continue;
}
if position == end {
lowest = cost;
continue;
}
// -1.rem_euclid(4) = 3
let left = (direction + 3) % 4;
let right = (direction + 1) % 4;
let next = [
(position + DIRECTIONS[direction], direction, cost + 1),
(position, left, cost + 1000),
(position, right, cost + 1000),
];
for tuple @ (next_position, next_direction, next_cost) in next {
if grid[next_position] != b'#' && next_cost < seen[next_position][next_direction] {
// Find the next bucket.
if next_direction == direction {
todo_first.push_back(tuple);
} else {
todo_second.push_back(tuple);
}
seen[next_position][next_direction] = next_cost;
}
}
}
(todo_first, todo_second) = (todo_second, todo_first);
}
// Backwards BFS
let mut todo = VecDeque::new();
let mut path = grid.same_size_with(false);
// Lowest paths can arrive at end node in multiple directions.
for direction in 0..4 {
if seen[end][direction] == lowest {
todo.push_back((end, direction, lowest));
}
}
while let Some((position, direction, cost)) = todo.pop_front() {
path[position] = true;
if position == start {
continue;
}
// Reverse direction and subtract cost.
let left = (direction + 3) % 4;
let right = (direction + 1) % 4;
let next = [
(position - DIRECTIONS[direction], direction, cost - 1),
(position, left, cost - 1000),
(position, right, cost - 1000),
];
for (next_position, next_direction, next_cost) in next {
// Trace our cost step by step so it will exactly match possible paths.
if next_cost == seen[next_position][next_direction] {
todo.push_back((next_position, next_direction, next_cost));
// Set cost back to `u32::MAX` to prevent redundant path explorations.
seen[next_position][next_direction] = u32::MAX;
}
}
}
(lowest, path.bytes.iter().filter(|&&b| b).count())
}
pub fn part1(input: &Input) -> u32 {
input.0
}
pub fn part2(input: &Input) -> usize {
input.1
}