@@ -21,14 +21,14 @@ struct Racetrack {
2121struct Cheat {
2222 start : Vec2 < i32 > ,
2323 end : Option < Vec2 < i32 > > ,
24- picos_left : i32 ,
24+ picos_left : usize ,
2525}
2626
2727#[ derive( Clone , Copy , Debug , Hash , PartialEq , Eq ) ]
2828struct Node {
2929 pos : Vec2 < i32 > ,
30- picos : i32 ,
31- cost : i32 ,
30+ picos : usize ,
31+ cost : usize ,
3232 cheat : Option < Cheat > ,
3333}
3434
@@ -46,10 +46,21 @@ impl PartialOrd for Node {
4646
4747#[ derive( Clone , Copy , Debug , Hash , PartialEq , Eq ) ]
4848enum CheatPolicy {
49- Allowed { picos : i32 } ,
49+ Allowed { picos : usize } ,
5050 Forbidden ,
5151}
5252
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+
5364impl Racetrack {
5465 fn height ( & self ) -> i32 {
5566 self . rows . len ( ) as i32
@@ -67,8 +78,8 @@ impl Racetrack {
6778 ( -1 ..=1 )
6879 . flat_map ( move |dy| ( -1 ..=1 )
6980 . 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) ) )
7283 }
7384
7485 fn locate ( & self , c : char ) -> Option < Vec2 < i32 > > {
@@ -78,7 +89,7 @@ impl Racetrack {
7889 }
7990
8091 /// 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 > {
8293 let mut stack = Vec :: new ( ) ;
8394 let mut distances = HashMap :: new ( ) ;
8495
@@ -92,60 +103,71 @@ impl Racetrack {
92103 }
93104
94105 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 ( ) } ) ;
96107 }
97108
98109 distances
99110 }
100111
101112 /// 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 {
103114 // Your run-of-the-mill A* (Dijkstra + heuristic) implementation
104115
116+ let skips = self . find_skips_to_end ( start, end) ;
105117 let mut queue = BinaryHeap :: new ( ) ;
106118 let mut visited = HashSet :: new ( ) ;
107- let mut paths = Vec :: new ( ) ;
119+ let mut paths = 0 ;
108120
109121 queue. push ( Node { pos : start, picos : 0 , cost : 0 , cheat : None } ) ;
110122 visited. insert ( ( start, None ) ) ;
111123
112124 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+
113128 if node. pos == end {
114- if !condition ( node) {
129+ if !condition ( node. picos ) {
115130 break ;
116131 }
117- println ! ( "{node:?}" ) ;
118- paths. push ( node) ;
132+ // println!("{}", skips[&start].len() - node.cost);
133+ paths += 1 ;
134+ continue ;
119135 }
120136
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+ }
135159 } else {
136- unreachable ! ( )
137- }
138- } else {
139- node. cheat
140- } ;
160+ node. cheat
161+ } ;
141162
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) ) ;
144165
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+ }
149171 }
150172 }
151173 }
@@ -175,14 +197,12 @@ fn main() {
175197 let start = track. locate ( 'S' ) . unwrap ( ) ;
176198 let end = track. locate ( 'E' ) . unwrap ( ) ;
177199
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 ( ) ;
180202
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) ;
183204 println ! ( "Part 1: {part1}" ) ;
184205
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) ;
187207 println ! ( "Part 2: {part2}" ) ;
188208}
0 commit comments