1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
//! # Packet Scanners
//!
//! This problem is similar to [`Year 2016 Day 15`]. However the key difference is that we need
//! to *avoid* the scanners, so the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
//! is not applicable.
//!
//! Part one checks that we can calculate the period for each scanner which is `2 * (range - 1)`.
//! For example a scanner with range 3 will be at the top position at time 0, 4, 8 and so on.
//!
//! To avoid a brute force approach for part two we sieve possible values for each scanner
//! sorted in ascending order of period. To combine the previous sieved values with the next
//! scanner, we find the [least common multiple](https://en.wikipedia.org/wiki/Least_common_multiple)
//! of their ranges. Then we "stretch" the sieved values to cover the new range, by adding
//! multiples of the factor between the previous range and the new lcm. Finally any values
//! that would collide with the new scanner are filtered out.
//!
//! Using the sample data:
//!
//! ```none
//! 0: 3 1 2
//! 1: 2 => 0 4
//! 4: 4 4 6
//! 6: 4 6 6
//! ```
//!
//! Starting value is `[1]`. First scanner:
//!
//! * Lcm of 1 and 2 => 2
//! * Stretch `[1] => [1+0 1+1] => [1 2]`
//! * Filter `[1 2] => [2]`
//!
//! Second scanner:
//!
//! * Lcm of 2 and 4 => 4
//! * Stretch `[2] => [2+0 2+2] => [2 4]`
//! * Filter `[2 4] => [2]`
//!
//! Third scanner:
//!
//! * Lcm of 4 and 6 => 12
//! * Stretch `[2] => [2+0 2+4 2+8] => [2 6 10]`
//! * Filter `[2 6 10] => [6 10]`
//!
//! Fourth scanner:
//!
//! * Lcm of 12 and 6 => 12
//! * Stretch `[6 10] => [6+0 10+0] => [6 10]`
//! * Filter `[6 10] => [10]`
//!
//! The lowest remaining value is our answer `10`.
//!
//! [`Year 2016 Day 15`]: crate::year2016::day15
use crate::util::iter::*;
use crate::util::math::*;
use crate::util::parse::*;
type Input = Vec<[u32; 2]>;
/// Sorts scanners in ascending order of range.
pub fn parse(input: &str) -> Input {
let mut scanners: Vec<_> = input.iter_unsigned().chunk::<2>().collect();
scanners.sort_unstable_by_key(|s| s[1]);
scanners
}
/// Leaving at time zero the packet will encounter each scanner at time `depth`.
pub fn part1(input: &Input) -> u32 {
let mut result = 0;
for &[depth, range] in input {
let period = 2 * (range - 1);
if depth % period == 0 {
result += depth * range;
}
}
result
}
/// Sieves possible values at each scanner stage to reduce the number of possible values.
pub fn part2(input: &Input) -> u32 {
let mut lcm = 1;
let mut current = &mut Vec::new();
let mut next = &mut Vec::new();
current.push(1);
for &[depth, range] in input {
// Find the least common multiple of the existing lcm and the new scanner period.
let period = 2 * (range - 1);
let next_lcm = lcm.lcm(period);
// Check each multiple of the current `end` against the new scanner.
for extra in (0..next_lcm).step_by(lcm as usize) {
for &delay in current.iter() {
if (delay + extra + depth) % period != 0 {
next.push(delay + extra);
}
}
}
lcm = next_lcm;
(current, next) = (next, current);
next.clear();
}
*current.first().unwrap()
}