1
+ //! # Sand Slabs
2
+ //!
3
+ //! Inspecting the input provides a useful insight. The x and y coordinates of bricks are
4
+ //! restricted to between to 0 and 9 inclusive so the final shape of the pile will resemble a tall
5
+ //! narrow tower similar to a [Jenga game](https://en.wikipedia.org/wiki/Jenga).
6
+ //!
7
+ //! A second insight is that this is a graph problem in disguise. Sorting the bricks in ascending
8
+ //! z order is equivalent to a [topological sort](https://en.wikipedia.org/wiki/Topological_sorting)
9
+ //! where each brick is a node and a directed edge links bricks that support other bricks.
10
+ //!
11
+ //! By iterating over each brick in order its final resting location and supporting bricks can be
12
+ //! calculated immediately. For example taking the first 3 example bricks:
13
+ //!
14
+ //! ```none
15
+ //! Brick Heights Indices
16
+ //!
17
+ //! 1,0,1~1,2,1 <- A 0 1 0 X 0 X Already in final position
18
+ //! 0 1 0 X 0 X
19
+ //! 0 1 0 X 0 X
20
+ //!
21
+ //! 0,0,2~2,0,2 <- B 2 2 2 1 1 1 Already in final position
22
+ //! 0 1 0 X 0 X
23
+ //! 0 1 0 X 0 X
24
+ //!
25
+ //! 0,2,3~2,2,3 <- C 2 2 2 1 1 1 Moves downwards by 1
26
+ //! 0 1 0 X 0 X
27
+ //! 2 2 2 2 2 2
28
+ //! ```
29
+ //!
30
+ //! ## Part One
31
+ //!
32
+ //! If a brick is supported by only a single brick then the parent brick cannot be safely removed
33
+ //! so we mark it as unsafe. Mutiple bricks could potentially be independently supported by a
34
+ //! single parent brick so using a boolean flag means that we won't overcount.
35
+ //!
36
+ //! ## Part Two
37
+ //!
38
+ //! Unsafe bricks are a [dominator](https://en.wikipedia.org/wiki/Dominator_(graph_theory)) in
39
+ //! graph theory as every path from the root (floor) to bricks supported by them must pass through
40
+ //! the unsafe node.
41
+ //!
42
+ //! To count the total number of bricks that fall when all unsafe bricks are removed one at a time
43
+ //! we build a linked list of bricks as we iterate through the nodes. Each brick has a `depth`
44
+ //! which is the number of unsafe "dominator" nodes that connect it to the root. For example:
45
+ //!
46
+ //! ```none
47
+ //!
48
+ //! Depth 0 1 2 1 0
49
+ //! | A ┬-> B --> C ┬-> D ┬-> E
50
+ //! | | | |
51
+ //! Floor | └-> F ------┘ |
52
+ //! | G ------------------┘
53
+ //! ```
54
+ //!
55
+ //! * `A` and `G` rest on the floor so their depth is 0 as they can never fall.
56
+ //! * `B` and `F` are both supported only by `A` so their depth is 1.
57
+ //! * `C` will fall if either `A` or `B` is removed so its depth is 2.
58
+ //! * `D` will only fall when `A` is removed. Removing `F` would leave it supported by `B` and `C`
59
+ //! or vice-versa. The common ancestor of the path to the root is `A` so its depth is 1.
60
+ //! * `E`'s common ancestor is the floor so its depth is 0.
61
+ //!
62
+ //! In total `0 (A) + 0 (G) + 1 (B) + 1 (F) + 2 (C) + 1 (D) + 0 (E) = 5` bricks will fall.
1
63
use crate :: util:: iter:: * ;
2
64
use crate :: util:: parse:: * ;
3
65
4
66
type Input = ( usize , usize ) ;
5
67
6
68
pub fn parse ( input : & str ) -> Input {
69
+ // Parse each brick into an array of 6 elements, one for each coordinate.
7
70
let mut bricks: Vec < _ > = input. iter_unsigned :: < usize > ( ) . chunk :: < 6 > ( ) . collect ( ) ;
71
+ // x and y are limited to 10 in each direction so we can use a fixed size array.
8
72
let mut heights = [ 0 ; 100 ] ;
9
73
let mut indices = [ usize:: MAX ; 100 ] ;
10
74
75
+ // Calculate the answer to both parts simultaneously for efficiency.
11
76
let mut safe = vec ! [ true ; bricks. len( ) ] ;
12
77
let mut dominator: Vec < ( usize , usize ) > = Vec :: with_capacity ( bricks. len ( ) ) ;
13
78
14
79
// Sort ascending by lowest z coordinate.
15
80
bricks. sort_unstable_by_key ( |b| b[ 2 ] ) ;
16
81
17
82
for ( i, & [ x1, y1, z1, x2, y2, z2] ) in bricks. iter ( ) . enumerate ( ) {
83
+ // Treat the 1D array as a 2D grid.
18
84
let start = 10 * y1 + x1;
19
85
let end = 10 * y2 + x2;
20
86
let step = if y2 > y1 { 10 } else { 1 } ;
21
87
let height = z2 - z1 + 1 ;
22
88
89
+ // Track what's underneath the brick.
23
90
let mut top = 0 ;
24
91
let mut previous = usize:: MAX ;
25
92
let mut underneath = 0 ;
26
93
let mut parent = 0 ;
27
94
let mut depth = 0 ;
28
95
96
+ // Find the highest z coordinate underneath the brick looking downwards along the z axis
97
+ // so only considering x and y coordinates.
29
98
for j in ( start..=end) . step_by ( step) {
30
99
top = top. max ( heights[ j] ) ;
31
100
}
@@ -44,12 +113,16 @@ pub fn parse(input: &str) -> Input {
44
113
let ( mut a, mut b) = ( parent, depth) ;
45
114
let ( mut x, mut y) = dominator[ previous] ;
46
115
116
+ // The depth must be the same.
47
117
while b > y {
48
118
( a, b) = dominator[ a] ;
49
119
}
50
120
while y > b {
51
121
( x, y) = dominator[ x] ;
52
122
}
123
+
124
+ // Bricks at the same depth could still have different paths from the
125
+ // root so we need to also check the indices match.
53
126
while a != x {
54
127
( a, b) = dominator[ a] ;
55
128
( x, _) = dominator[ x] ;
@@ -60,10 +133,12 @@ pub fn parse(input: &str) -> Input {
60
133
}
61
134
}
62
135
136
+ // Update the x-y grid underneath the brick the with the new highest point and index.
63
137
heights[ j] = top + height;
64
138
indices[ j] = i;
65
139
}
66
140
141
+ // Increase depth by one for each dominator node in the path from the root.
67
142
if underneath == 1 {
68
143
safe[ previous] = false ;
69
144
parent = previous;
0 commit comments