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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
//! # Infinite Elves and Infinite Houses
//!
//! The amount of presents that each house receives in part one is 10 times the
//! [divisor function](https://en.wikipedia.org/wiki/Divisor_function) `σ`.
//! For example the divisors of 6 are 1, 2, 3 and 6, so house 6 receives
//! 10 + 20 + 30 + 60 = 120 presents.
//!
//! It's highly likely that the answer will be a
//! [superabundant number](https://en.wikipedia.org/wiki/Superabundant_number) but there's no way
//! to easily *prove* that so we brute force check every possible solution. The approach is similar
//! to a reverse [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes),
//! iterating first over each elf, then over each house adding the presents.
//! Somewhat unintuitively the `ln(x)` asymptotic complexity of this approach is much lower
//! than the `n√n` complexity of finding the factors of each number.
//!
//! To speed things up we make one high level optimization and a few low tweaks.
//!
//! The high level optimization is an observation from user `warbaque` on the
//! [Day 20 solution thread](https://www.reddit.com/r/adventofcode/comments/3xjpp2/day_20_solutions/)
//! that [Robin's inequality](https://en.wikipedia.org/wiki/Divisor_function#Growth_rate)
//! shows that the `σ(n)` function is lower than `eᵞ * n * ln(ln(n))` for all `n` greater than 5040.
//! This means that we can determine a starting index close to the final result, skipping over a
//! huge chunk of numbers.
//!
//! The low level tweaks reduce the amount of work that needs to be done.
//! We break the search range into fixed size blocks from `start` to `end`,
//! for example 200000 to 299999 with a block size of 100000. Then we can make some observations:
//!
//! * Elf 1 visits each house once.
//! * Elves from `start` to `end` each visit a different house exactly once,
//! bringing start, start + 1, ... presents.
//! * Elves from `end / 2` to `start` skip over all the houses.
//! * Elves from `block size` to `end / 2` visit *at most* one house as the increment is
//! greater than the size of the block.
//! * Elves from `2` to `block size` may visit any number of times.
// More explicit syntax fits in with surrounding code better.
#![allow(clippy::needless_range_loop)]
use crate::util::parse::*;
const BLOCK: usize = 100_000;
type Input = (u32, usize);
pub fn parse(input: &str) -> Input {
let robins_inequality = [
(100000, 4352000),
(200000, 8912250),
(300000, 13542990),
(400000, 18218000),
(500000, 22925240),
(600000, 27657740),
(700000, 32410980),
(800000, 37181790),
(900000, 41967820),
(1000000, 46767260),
(1100000, 51578680),
(1200000, 56400920),
(1300000, 61233020),
(1400000, 66074170),
(1500000, 70923680),
(1600000, 75780960),
(1700000, 80645490),
(1800000, 85516820),
(1900000, 90394550),
(2000000, 95278320),
];
// Find a starting block closer to the answer. Part two presents overall are lower than
// part one so the answer will also be after this block.
let (target, mut start) = (input.unsigned(), 0);
for (key, value) in robins_inequality {
if target >= value {
start = key;
} else {
break;
}
}
assert!(target > 5040);
assert!(start > 100000);
(target, start)
}
pub fn part1(input: &Input) -> usize {
let (target, mut start) = *input;
let mut end = start + BLOCK;
let mut houses = vec![0; BLOCK];
loop {
// Elves that visit exactly once.
for i in 0..BLOCK {
houses[i] = 10 * (1 + start + i) as u32;
}
// Elves that visit at most once.
for i in BLOCK..end / 2 {
let presents = 10 * i as u32;
let j = start.next_multiple_of(i) - start;
if j < BLOCK {
houses[j] += presents;
}
}
// All remaining elves.
for i in 2..BLOCK {
let presents = 10 * i as u32;
let mut j = start.next_multiple_of(i) - start;
while j < BLOCK {
houses[j] += presents;
j += i;
}
}
if let Some(found) = houses.iter().position(|&p| p >= target) {
break start + found;
}
start += BLOCK;
end += BLOCK;
}
}
pub fn part2(input: &Input) -> usize {
let (target, mut start) = *input;
let mut end = start + BLOCK;
let mut houses = vec![0; BLOCK];
loop {
// Elves that visit exactly once (not including elf 1 anymore).
for i in 0..BLOCK {
houses[i] = 11 * (start + i) as u32;
}
// Elves that visit at most once.
for i in BLOCK..end / 2 {
let presents = 11 * i as u32;
let j = start.next_multiple_of(i) - start;
if j < BLOCK {
houses[j] += presents;
}
}
// All remaining elves. We can start higher than 2.
for i in start / 50..BLOCK {
let presents = 11 * i as u32;
let mut j = start.next_multiple_of(i) - start;
let mut remaining = 51 - start.div_ceil(i);
while j < BLOCK && remaining > 0 {
houses[j] += presents;
j += i;
remaining -= 1;
}
}
if let Some(found) = houses.iter().position(|&p| p >= target) {
break start + found;
}
start += BLOCK;
end += BLOCK;
}
}