11//! # Tractor Beam
22//!
3- //! Finds the approximate boundary of the upper and lower edges of the beam expressed as a slope.
4- //! We then skip the relatively expensive intcode test if the x and y coordinates lie outside.
3+ //! The intcode program computes a linear inequality: returning true if an integer point lies on
4+ //! or between two lines with irrational slope. The intcode program was designed so that the two
5+ //! lines are narrow enough that there are no integer solutions when `y=1`, so there are
6+ //! intentionally one or two discontinuities between the origin and the bulk of the beam. This
7+ //! solution finds the approximate boundary of the upper and lower edges of the beam expressed
8+ //! as an integer ratio for slope. We then skip the relatively expensive intcode test if the x
9+ //! and y coordinates lie outside. Once we identify an edge past the initial discontinuities,
10+ //! scaling along the lines buys more accuracy and thus fewer later intcode runs.
11+ //!
12+ //! For part 2, we can further speed up the process by using geometry to hone in on a viable
13+ //! target to start searching at. If we consider a scaled-down beam with target point A on slope
14+ //! upper as (x1, y1), and point B on slope lower as (x2, y2), the bounding box has a top left
15+ //! corner at point S at (x2, y1):
16+ //! ```none
17+ //! 1
18+ //! 11
19+ //! 111
20+ //! 1111
21+ //! 11111
22+ //! 111111
23+ //! 1111111
24+ //! 11111111
25+ //! 111SxxA11 <--- (x1, y1)
26+ //! 11xxxx1111
27+ //! 1xxxx111111
28+ //! (x2, y2)> Bxxx11111111
29+ //! 1111111111111
30+ //! ```
31+ //!
32+ //! For a 100x100 box, we have a system of equations x1=m1≈upper/scale, x1=x2+99, y2=m2≈scale/lower,
33+ //! and y2=y1+99, which we solve as:
34+ //! ```none
35+ //! x2 = ((m1 * 99 + 99) / (m2 - m1)
36+ //! ≈ ((upper/scale * 99) + 99) / (scale/lower - upper/scale)
37+ //! ≈ ((99*upper/scale) + (99*scale/scale)) / ((scale*scale/scale*lower - lower*upper/scale*lower))
38+ //! ≈ ((99 * (upper + scale)) * (scale*lower)) / (scale * (scale*scale - lower*upper))
39+ //! ≈ (99*lower * (upper + scale)) / (scale*scale - lower*upper)
40+ //! y1 = m2 * x2 - 99
41+ //! ≈ scale*x2 / lower - 99
42+ //! ```
543use super :: intcode:: * ;
644use crate :: util:: parse:: * ;
745
846pub struct Input {
947 code : Vec < i64 > ,
10- lower : i64 ,
11- upper : i64 ,
48+ scale : i64 ,
49+ lower : i64 , // slope scale/lower just outside left boundary
50+ upper : i64 , // slope upper/scale just outside right boundary
1251}
1352
1453pub fn parse ( input : & str ) -> Input {
54+ // Pick an initial scale large enough to be past the discontinuities for all known inputs.
1555 let code: Vec < _ > = input. iter_signed ( ) . collect ( ) ;
16- let mut lower = 0 ;
17- let mut upper = 0 ;
56+ let mut lower = 1 ;
57+ let mut upper = 1 ;
58+ let mut scale = 5 ;
1859
19- // Find slope of lower and upper edges, rounding down to prevent false negatives.
20- while !test ( & code, lower + 1 , 50 ) {
21- lower += 1 ;
22- }
23- while !test ( & code, 50 , upper + 1 ) {
24- upper += 1 ;
60+ // Find approximate slope of lower and upper edges, rounding down to prevent false negatives.
61+ // Scale the boundary for slightly more accuracy.
62+ while scale < 1024 {
63+ scale *= 2 ;
64+ lower *= 2 ;
65+ upper *= 2 ;
66+ while !test ( & code, lower + 1 , scale) {
67+ lower += 1 ;
68+ }
69+ while !test ( & code, scale, upper + 1 ) {
70+ upper += 1 ;
71+ }
2572 }
2673
27- Input { code, lower, upper }
74+ Input { code, scale , lower, upper }
2875}
2976
3077pub fn part1 ( input : & Input ) -> i64 {
3178 let code = & input. code ;
32- // Handle origin specially .
33- let mut result = test ( code , 0 , 0 ) as i64 ;
79+ // The origin is always set, and no other point on that row .
80+ let mut result = 1 ;
3481
35- // The beam is continuous so we only need to find the left and right edges.
36- for y in 0 ..50 {
37- let left = ( 0 ..50 ) . find ( |& x| precheck ( input, x, y) && test ( code, x, y) ) ;
38- let right = ( 0 ..50 ) . rfind ( |& x| precheck ( input, x, y) && test ( code, x, y) ) ;
82+ // Scanning the remaining lines works even around the known discontinuity at y=1, by finding
83+ // the left and right edges if any.
84+ for y in 2 ..50 {
85+ let left = ( 1 ..50 ) . find ( |& x| precheck ( input, x, y) && test ( code, x, y) ) ;
86+ let right = ( 1 ..50 ) . rfind ( |& x| precheck ( input, x, y) && test ( code, x, y) ) ;
3987 if let ( Some ( l) , Some ( r) ) = ( left, right) {
4088 result += r - l + 1 ;
4189 }
@@ -45,9 +93,11 @@ pub fn part1(input: &Input) -> i64 {
4593}
4694
4795pub fn part2 ( input : & Input ) -> i64 {
96+ // See comments above about derivation of initial guess for x and y.
4897 let code = & input. code ;
49- let mut x = 0 ;
50- let mut y = 0 ;
98+ let mut x = 99 * input. lower * ( input. upper + input. scale )
99+ / ( input. scale * input. scale - input. lower * input. upper ) ;
100+ let mut y = input. scale * x / input. lower - 99 ;
51101 let mut moved = true ;
52102
53103 // Increase the right and bottom edges of our box until they are both inside the beam.
@@ -70,7 +120,7 @@ pub fn part2(input: &Input) -> i64 {
70120
71121/// Quick check with some false positives but no false negatives.
72122fn precheck ( input : & Input , x : i64 , y : i64 ) -> bool {
73- 50 * y > input. upper * x && 50 * x > input. lower * y
123+ input . scale * y > input. upper * x && input . scale * x > input. lower * y
74124}
75125
76126/// Definitive but slower check.
0 commit comments