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
//! # A Maze of Twisty Trampolines, All Alike
//!
//! Part one brute forces the jumps. For part two we can make an observation that the jumps offsets
//! will eventually flip flop between 2 or 3 starting from the beginning, for example:
//!
//! ```none
//! 2 3 2 3 -1
//! ```
//!
//! The twos and threes can be represented in binary compact form, using 0 for 2 and 1 for 3:
//!
//! ```none
//! 0101
//! ```
//!
//! We then precompute all possible combination for blocks of size 16, using this to accelerate
//! part two.
use crate::util::parse::*;
const WIDTH: usize = 16;
const LENGTH: usize = 1 << WIDTH;
pub fn parse(input: &str) -> Vec<i32> {
input.iter_signed().collect()
}
/// Brute force implementation.
pub fn part1(input: &[i32]) -> usize {
let mut jump = input.to_vec();
let mut total = 0;
let mut index = 0;
while index < jump.len() {
let next = index.wrapping_add(jump[index] as usize);
jump[index] += 1;
total += 1;
index = next;
}
total
}
#[expect(clippy::needless_range_loop)]
pub fn part2(input: &[i32]) -> usize {
let mut jump = input.to_vec();
let mut total = 0;
let mut index = 0;
let mut fine = 0;
let mut coarse = 0;
let mut compact = Vec::new();
let mut cache = vec![[(0_u16, 0_u8, 0_u8); LENGTH]; WIDTH];
// Precompute all possible combinations. For each binary starting number we can start at any
// offset from 0..16.
for i in 0..WIDTH {
for j in 0..LENGTH {
let mut offset = i as u16;
let mut value = j as u16;
let mut steps = 0;
while offset < 16 {
value ^= 1 << offset;
steps += 1;
offset += 3 - ((value >> offset) & 1);
}
cache[i][j] = (value, steps, offset as u8 - i as u8);
}
}
while index < jump.len() {
if index < coarse {
// Index lies withing precomputed blocks.
let base = index / 16;
let offset = index % 16;
let value = compact[base] as usize;
let (next, steps, delta) = cache[offset][value];
compact[base] = next;
total += steps as usize;
index += delta as usize;
} else {
// Fall back to part one approach.
let next = index.wrapping_add(jump[index] as usize);
jump[index] += if jump[index] == 3 { -1 } else { 1 };
total += 1;
// The frontier of twos and threes advances through the jump offsets.
// Each time it crosses a block of 16 add to the compact binary representation.
if jump[index] == 2 && index == fine {
fine += 1;
if fine % 16 == 0 {
let value = (coarse..fine).rev().fold(0, |acc, i| (acc << 1) | (jump[i] & 1));
coarse = fine;
compact.push(value as u16);
}
}
index = next;
}
}
total
}