1
+ //! # Step Counter
2
+ //!
3
+ //! This solution uses a geometric approach. Looking at the input data reveals several crucial
4
+ //! insights:
5
+ //!
6
+ //! * The sample data is a decoy and will not work with this solution.
7
+ //! * The real input data has two special properties:
8
+ //! * Vertical and horizontal "roads" run from the center.
9
+ //! * The edge of the input is completely free of obstructions.
10
+ //!
11
+ //! These properties mean that we can always cross a tile in exactly 131 steps. We start in the
12
+ //! middle of a tile and need 65 steps to reach the edge. Part two asks how many plots can be
13
+ //! reached in 26501365 steps.
14
+ //!
15
+ //! ```none
16
+ //! 26501365 => 65 + 131 * n => n = 202300
17
+ //! ```
18
+ //!
19
+ //! The number of tiles that we can reach forms a rough diamond 202300 tiles wide.
20
+ //! For example `n = 2` looks like:
21
+ //!
22
+ //! ```none
23
+ //! #
24
+ //! ###
25
+ //! #####
26
+ //! ###
27
+ //! #
28
+ //! ```
29
+ //!
30
+ //! The next insight is that if we can reach a plot in `x` steps the we can also reach it in
31
+ //! `x + 2, x + 4...` steps by repeatedly stepping back and forth 1 tile. This means the
32
+ //! number of tiles reachable depends on the *parity* of a plot from the center,
33
+ //! i.e. whether it is an odd or even number of steps. As the 131 width of the tile is an odd
34
+ //! number of plots, the number of plots reachable flips from odd to even each time we cross a
35
+ //! whole tile. There are `n²` even plots and `(n + 1)²` odd plots in the diamond.
36
+ //!
37
+ //! ```none
38
+ //! O
39
+ //! OEO
40
+ //! OEOEO
41
+ //! OEO
42
+ //! O
43
+ //! ```
44
+ //!
45
+ //! Lastly, we can only partially reach some tiles on the edges. Solid triangles represent corners
46
+ //! that can be reached and hollow triangle represents corners that are too far away.
47
+ //!
48
+ //! ```none
49
+ //! ┌--┐
50
+ //! |◸◹|
51
+ //! ◢| |◣
52
+ //! ┌--┼--┼--┐
53
+ //! |◸ | | ◹|
54
+ //! ◢| | | |◣
55
+ //! ┌--┼--┼--┼--┼--┐
56
+ //! |◸ | | | | ◹|
57
+ //! |◺ | | | | ◿|
58
+ //! └--┼--┼--┼--┼--┘
59
+ //! ◥| | | |◤
60
+ //! |◺ | | ◿|
61
+ //! └--┼--┼--┘
62
+ //! ◥| |◤
63
+ //! |◺◿|
64
+ //! └--┘
65
+ //! ```
66
+ //!
67
+ //! The total area is adjusted by:
68
+ //! * Adding `n` extra even corners
69
+ //! ```none
70
+ //! ◤◥
71
+ //! ◣◢
72
+ //! ```
73
+ //! * Subtracting `n + 1` odd corners
74
+ //! ```none
75
+ //! ◸◹
76
+ //! ◺◿
77
+ //! ```
78
+ //!
79
+ //! To find the values for the total number of odd, even plots and the unreachable odd corners
80
+ //! we BFS from the center tile, counting odd and even plots separately. Any plots more than
81
+ //! 65 steps from the center will be unreachable at the edges of the diamond.
82
+ //!
83
+ //! One nuance is that to always correctly find the extra reachable even corner plots requires a
84
+ //! *second* BFS starting from the corners and working inwards. All tiles within 64 steps are
85
+ //! reachable at the edges of the diamond. For some inputs this happens to be the same as the number
86
+ //! of tiles greater than 65 steps from the center by coincidence, however this is not guaranteed so
87
+ //! a second BFS is more reliable solution.
1
88
use crate :: util:: grid:: * ;
2
89
use crate :: util:: point:: * ;
3
90
use std:: collections:: VecDeque ;
@@ -11,15 +98,18 @@ type Input = (u64, u64);
11
98
pub fn parse ( input : & str ) -> Input {
12
99
let grid = Grid :: parse ( input) ;
13
100
101
+ // Search from the center tile outwards.
14
102
let ( even_inner, even_outer, odd_inner, odd_outer) = bfs ( & grid, & [ CENTER ] , 130 ) ;
15
103
let part_one = even_inner;
16
104
let even_full = even_inner + even_outer;
17
105
let odd_full = odd_inner + odd_outer;
18
106
let remove_corners = odd_outer;
19
107
108
+ // Search from the 4 corners inwards.
20
109
let ( even_inner, ..) = bfs ( & grid, & CORNERS , 64 ) ;
21
110
let add_corners = even_inner;
22
111
112
+ // Sum the components of the diamond.
23
113
let n = 202300 ;
24
114
let first = n * n * even_full;
25
115
let second = ( n + 1 ) * ( n + 1 ) * odd_full;
@@ -38,6 +128,7 @@ pub fn part2(input: &Input) -> u64 {
38
128
input. 1
39
129
}
40
130
131
+ /// Breadth first search from any number of starting locations with a limit on maximum steps.
41
132
fn bfs ( grid : & Grid < u8 > , starts : & [ Point ] , limit : u32 ) -> ( u64 , u64 , u64 , u64 ) {
42
133
let mut grid = grid. clone ( ) ;
43
134
let mut todo = VecDeque :: new ( ) ;
@@ -53,6 +144,7 @@ fn bfs(grid: &Grid<u8>, starts: &[Point], limit: u32) -> (u64, u64, u64, u64) {
53
144
}
54
145
55
146
while let Some ( ( position, cost) ) = todo. pop_front ( ) {
147
+ // First split by odd or even parity then by distance from the starting point.
56
148
if cost % 2 == 1 {
57
149
if position. manhattan ( CENTER ) <= 65 {
58
150
odd_inner += 1 ;
0 commit comments