-
Notifications
You must be signed in to change notification settings - Fork 4
/
day22.rs
162 lines (144 loc) · 6.24 KB
/
day22.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! # Sand Slabs
//!
//! Inspecting the input provides a useful insight. The x and y coordinates of bricks are
//! restricted to between to 0 and 9 inclusive so the final shape of the pile will resemble a tall
//! narrow tower similar to a [Jenga game](https://en.wikipedia.org/wiki/Jenga).
//!
//! A second insight is that this is a graph problem in disguise. Sorting the bricks in ascending
//! z order is equivalent to a [topological sort](https://en.wikipedia.org/wiki/Topological_sorting)
//! where each brick is a node and a directed edge links bricks that support other bricks.
//!
//! By iterating over each brick in order its final resting location and supporting bricks can be
//! calculated immediately. For example taking the first 3 example bricks:
//!
//! ```none
//! Brick Heights Indices
//!
//! 1,0,1~1,2,1 <- A 0 1 0 X 0 X Already in final position
//! 0 1 0 X 0 X
//! 0 1 0 X 0 X
//!
//! 0,0,2~2,0,2 <- B 2 2 2 1 1 1 Already in final position
//! 0 1 0 X 0 X
//! 0 1 0 X 0 X
//!
//! 0,2,3~2,2,3 <- C 2 2 2 1 1 1 Moves downwards by 1
//! 0 1 0 X 0 X
//! 2 2 2 2 2 2
//! ```
//!
//! ## Part One
//!
//! If a brick is supported by only a single brick then the parent brick cannot be safely removed
//! so we mark it as unsafe. Mutiple bricks could potentially be independently supported by a
//! single parent brick so using a boolean flag means that we won't overcount.
//!
//! ## Part Two
//!
//! Unsafe bricks are a [dominator](https://en.wikipedia.org/wiki/Dominator_(graph_theory)) in
//! graph theory as every path from the root (floor) to bricks supported by them must pass through
//! the unsafe node.
//!
//! To count the total number of bricks that fall when all unsafe bricks are removed one at a time
//! we build a linked list of bricks as we iterate through the nodes. Each brick has a `depth`
//! which is the number of unsafe "dominator" nodes that connect it to the root. For example:
//!
//! ```none
//!
//! Depth 0 1 2 1 0
//! | A ┬-> B --> C ┬-> D ┬-> E
//! | | | |
//! Floor | └-> F ------┘ |
//! | G ------------------┘
//! ```
//!
//! * `A` and `G` rest on the floor so their depth is 0 as they can never fall.
//! * `B` and `F` are both supported only by `A` so their depth is 1.
//! * `C` will fall if either `A` or `B` is removed so its depth is 2.
//! * `D` will only fall when `A` is removed. Removing `F` would leave it supported by `B` and `C`
//! or vice-versa. The common ancestor of the path to the root is `A` so its depth is 1.
//! * `E`'s common ancestor is the floor so its depth is 0.
//!
//! In total `0 (A) + 0 (G) + 1 (B) + 1 (F) + 2 (C) + 1 (D) + 0 (E) = 5` bricks will fall.
use crate::util::iter::*;
use crate::util::parse::*;
type Input = (usize, usize);
pub fn parse(input: &str) -> Input {
// Parse each brick into an array of 6 elements, one for each coordinate.
let mut bricks: Vec<_> = input.iter_unsigned::<usize>().chunk::<6>().collect();
// x and y are limited to 10 in each direction so we can use a fixed size array.
let mut heights = [0; 100];
let mut indices = [usize::MAX; 100];
// Calculate the answer to both parts simultaneously for efficiency.
let mut safe = vec![true; bricks.len()];
let mut dominator: Vec<(usize, usize)> = Vec::with_capacity(bricks.len());
// Sort ascending by lowest z coordinate.
bricks.sort_unstable_by_key(|b| b[2]);
for (i, &[x1, y1, z1, x2, y2, z2]) in bricks.iter().enumerate() {
// Treat the 1D array as a 2D grid.
let start = 10 * y1 + x1;
let end = 10 * y2 + x2;
let step = if y2 > y1 { 10 } else { 1 };
let height = z2 - z1 + 1;
// Track what's underneath the brick.
let mut top = 0;
let mut previous = usize::MAX;
let mut underneath = 0;
let mut parent = 0;
let mut depth = 0;
// Find the highest z coordinate underneath the brick looking downwards along the z axis
// so only considering x and y coordinates.
for j in (start..=end).step_by(step) {
top = top.max(heights[j]);
}
for j in (start..=end).step_by(step) {
if heights[j] == top {
let index = indices[j];
if index != previous {
previous = index;
underneath += 1;
if underneath == 1 {
(parent, depth) = dominator[previous];
} else {
// Find common ancestor
let (mut a, mut b) = (parent, depth);
let (mut x, mut y) = dominator[previous];
// The depth must be the same.
while b > y {
(a, b) = dominator[a];
}
while y > b {
(x, y) = dominator[x];
}
// Bricks at the same depth could still have different paths from the
// root so we need to also check the indices match.
while a != x {
(a, b) = dominator[a];
(x, _) = dominator[x];
}
(parent, depth) = (a, b);
}
}
}
// Update the x-y grid underneath the brick the with the new highest point and index.
heights[j] = top + height;
indices[j] = i;
}
// Increase depth by one for each dominator node in the path from the root.
if underneath == 1 {
safe[previous] = false;
parent = previous;
depth = dominator[previous].1 + 1;
}
dominator.push((parent, depth));
}
let part_one = safe.iter().filter(|&&b| b).count();
let part_two = dominator.iter().map(|(_, d)| d).sum();
(part_one, part_two)
}
pub fn part1(input: &Input) -> usize {
input.0
}
pub fn part2(input: &Input) -> usize {
input.1
}