@@ -21,14 +21,14 @@ struct Racetrack {
21
21
struct Cheat {
22
22
start : Vec2 < i32 > ,
23
23
end : Option < Vec2 < i32 > > ,
24
- picos_left : i32 ,
24
+ picos_left : usize ,
25
25
}
26
26
27
27
#[ derive( Clone , Copy , Debug , Hash , PartialEq , Eq ) ]
28
28
struct Node {
29
29
pos : Vec2 < i32 > ,
30
- picos : i32 ,
31
- cost : i32 ,
30
+ picos : usize ,
31
+ cost : usize ,
32
32
cheat : Option < Cheat > ,
33
33
}
34
34
@@ -46,10 +46,21 @@ impl PartialOrd for Node {
46
46
47
47
#[ derive( Clone , Copy , Debug , Hash , PartialEq , Eq ) ]
48
48
enum CheatPolicy {
49
- Allowed { picos : i32 } ,
49
+ Allowed { picos : usize } ,
50
50
Forbidden ,
51
51
}
52
52
53
+ #[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
54
+ struct Skip {
55
+ positions : Vec < Vec2 < i32 > > ,
56
+ }
57
+
58
+ impl Skip {
59
+ fn len ( & self ) -> usize {
60
+ self . positions . len ( )
61
+ }
62
+ }
63
+
53
64
impl Racetrack {
54
65
fn height ( & self ) -> i32 {
55
66
self . rows . len ( ) as i32
@@ -67,8 +78,8 @@ impl Racetrack {
67
78
( -1 ..=1 )
68
79
. flat_map ( move |dy| ( -1 ..=1 )
69
80
. filter ( move |& dx| ( dx != 0 ) ^ ( dy != 0 ) )
70
- . map ( move |dx| Vec2 :: new ( pos. x + dx, pos. y + dy) ) )
71
- . filter ( move |& neigh| self . in_bounds ( neigh) )
81
+ . map ( move |dx| Vec2 :: new ( pos. x + dx, pos. y + dy) )
82
+ . filter ( move |& neigh| self . in_bounds ( neigh) ) )
72
83
}
73
84
74
85
fn locate ( & self , c : char ) -> Option < Vec2 < i32 > > {
@@ -78,7 +89,7 @@ impl Racetrack {
78
89
}
79
90
80
91
/// Traces out the racetrack, finding the distances to the given end.
81
- fn find_distances_to_end ( & self , start : Vec2 < i32 > , end : Vec2 < i32 > ) -> HashMap < Vec2 < i32 > , i32 > {
92
+ fn find_skips_to_end ( & self , start : Vec2 < i32 > , end : Vec2 < i32 > ) -> HashMap < Vec2 < i32 > , Skip > {
82
93
let mut stack = Vec :: new ( ) ;
83
94
let mut distances = HashMap :: new ( ) ;
84
95
@@ -92,60 +103,71 @@ impl Racetrack {
92
103
}
93
104
94
105
for ( i, & pos) in stack. iter ( ) . enumerate ( ) {
95
- distances. insert ( pos, ( stack. len ( ) - 1 - i ) as i32 ) ;
106
+ distances. insert ( pos, Skip { positions : stack[ ( i + 1 ) .. ] . iter ( ) . cloned ( ) . collect ( ) } ) ;
96
107
}
97
108
98
109
distances
99
110
}
100
111
101
112
/// Finds paths through the racetrack, ordered ascendingly by total picoseconds.
102
- fn find_paths ( & self , start : Vec2 < i32 > , end : Vec2 < i32 > , cheat_policy : CheatPolicy , condition : impl Fn ( Node ) -> bool ) -> Vec < Node > {
113
+ fn count_paths ( & self , start : Vec2 < i32 > , end : Vec2 < i32 > , cheat_policy : CheatPolicy , condition : impl Fn ( usize ) -> bool ) -> i32 {
103
114
// Your run-of-the-mill A* (Dijkstra + heuristic) implementation
104
115
116
+ let skips = self . find_skips_to_end ( start, end) ;
105
117
let mut queue = BinaryHeap :: new ( ) ;
106
118
let mut visited = HashSet :: new ( ) ;
107
- let mut paths = Vec :: new ( ) ;
119
+ let mut paths = 0 ;
108
120
109
121
queue. push ( Node { pos : start, picos : 0 , cost : 0 , cheat : None } ) ;
110
122
visited. insert ( ( start, None ) ) ;
111
123
112
124
while let Some ( node) = queue. pop ( ) {
125
+ let cheats_allowed = matches ! ( cheat_policy, CheatPolicy :: Allowed { .. } ) ;
126
+ let can_cheat = cheats_allowed && node. cheat . map_or ( true , |c| c. picos_left > 0 ) ;
127
+
113
128
if node. pos == end {
114
- if !condition ( node) {
129
+ if !condition ( node. picos ) {
115
130
break ;
116
131
}
117
- println ! ( "{node:?}" ) ;
118
- paths. push ( node) ;
132
+ // println!("{}", skips[&start].len() - node.cost);
133
+ paths += 1 ;
134
+ continue ;
119
135
}
120
136
121
- for neigh in self . neighbors ( node. pos ) {
122
- let is_wall = self [ neigh] == '#' ;
123
-
124
- let cheats_allowed = matches ! ( cheat_policy, CheatPolicy :: Allowed { .. } ) ;
125
- let can_cheat = cheats_allowed && node. cheat . map_or ( true , |c| c. picos_left > 0 ) ;
126
- let new_cheat = if let Some ( cheat) = node. cheat {
127
- let is_ending = cheat. picos_left == 1 ;
128
- if is_ending && is_wall {
129
- continue ;
130
- }
131
- Some ( Cheat { start : cheat. start , end : if is_ending { Some ( neigh) } else { cheat. end } , picos_left : ( cheat. picos_left - 1 ) . max ( 0 ) } )
132
- } else if cheats_allowed && is_wall {
133
- if let CheatPolicy :: Allowed { picos : cheat_picos } = cheat_policy {
134
- Some ( Cheat { start : node. pos , end : None , picos_left : cheat_picos } )
137
+ if !can_cheat && skips. contains_key ( & node. pos ) {
138
+ // Skip the intermediate positions and hop straight to the end
139
+ let skip = & skips[ & node. pos ] ;
140
+ let new_picos = node. picos + skip. len ( ) ;
141
+ queue. push ( Node { pos : end, cost : new_picos, picos : new_picos, cheat : node. cheat } ) ;
142
+ } else {
143
+ // Search as usual
144
+ for neigh in self . neighbors ( node. pos ) {
145
+ let is_wall = self [ neigh] == '#' ;
146
+
147
+ let new_cheat = if let Some ( cheat) = node. cheat {
148
+ let is_ending = cheat. picos_left == 1 ;
149
+ if is_ending && is_wall {
150
+ continue ;
151
+ }
152
+ Some ( Cheat { start : cheat. start , end : if is_ending { Some ( neigh) } else { cheat. end } , picos_left : cheat. picos_left . max ( 1 ) - 1 } )
153
+ } else if cheats_allowed && is_wall {
154
+ if let CheatPolicy :: Allowed { picos : cheat_picos } = cheat_policy {
155
+ Some ( Cheat { start : node. pos , end : None , picos_left : cheat_picos - 1 } )
156
+ } else {
157
+ unreachable ! ( )
158
+ }
135
159
} else {
136
- unreachable ! ( )
137
- }
138
- } else {
139
- node. cheat
140
- } ;
160
+ node. cheat
161
+ } ;
141
162
142
- if !visited. contains ( & ( neigh, new_cheat) ) && ( !is_wall || can_cheat) {
143
- visited. insert ( ( neigh, new_cheat) ) ;
163
+ if !visited. contains ( & ( neigh, new_cheat) ) && ( !is_wall || can_cheat) {
164
+ visited. insert ( ( neigh, new_cheat) ) ;
144
165
145
- let new_picos = node. picos + 1 ;
146
- let new_dist_to_end = ( neigh. x . abs_diff ( end. x ) + neigh. y . abs_diff ( end. y ) ) as i32 ;
147
- let new_cost = new_picos + new_dist_to_end;
148
- queue. push ( Node { pos : neigh, picos : new_picos, cost : new_cost, cheat : new_cheat } ) ;
166
+ let new_picos = node. picos + 1 ;
167
+ let new_dist_to_end = ( neigh. x . abs_diff ( end. x ) + neigh. y . abs_diff ( end. y ) ) as usize ;
168
+ let new_cost = new_picos + new_dist_to_end;
169
+ queue. push ( Node { pos : neigh, picos : new_picos, cost : new_cost, cheat : new_cheat } ) ;
170
+ }
149
171
}
150
172
}
151
173
}
@@ -175,14 +197,12 @@ fn main() {
175
197
let start = track. locate ( 'S' ) . unwrap ( ) ;
176
198
let end = track. locate ( 'E' ) . unwrap ( ) ;
177
199
178
- let distances_to_end = track. find_distances_to_end ( start, end) ;
179
- let base_picos = distances_to_end [ & start] ;
200
+ let skips = track. find_skips_to_end ( start, end) ;
201
+ let base_picos = skips [ & start] . len ( ) ;
180
202
181
- let cheat_paths1 = track. find_paths ( start, end, CheatPolicy :: Allowed { picos : 2 } , |n| n. picos <= base_picos - 100 ) ;
182
- let part1 = cheat_paths1. len ( ) ;
203
+ let part1 = track. count_paths ( start, end, CheatPolicy :: Allowed { picos : 2 } , |picos| picos + 100 <= base_picos) ;
183
204
println ! ( "Part 1: {part1}" ) ;
184
205
185
- let cheat_paths2 = track. find_paths ( start, end, CheatPolicy :: Allowed { picos : 20 } , |n| n. picos <= base_picos - 100 ) ;
186
- let part2 = cheat_paths2. len ( ) ;
206
+ let part2 = track. count_paths ( start, end, CheatPolicy :: Allowed { picos : 20 } , |picos| picos + 100 <= base_picos) ;
187
207
println ! ( "Part 2: {part2}" ) ;
188
208
}
0 commit comments