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
//! # Experimental Emergency Teleportation
//!
//! Part two implements a 3D version of binary search. Starting with a single cube that encloses all
//! nanbots, each cube is further split into 8 smaller cubes until we find the answer.
//! Cubes are stored in a [`MinHeap`] ordered by:
//!
//! * Greatest number of nanobots in range.
//! * Least distance to origin.
//! * Least size.
//!
//! This means that when we encounter a cube of size 1 we can return the coordinates,
//! since we know that:
//!
//! * There are no cubes within range of more nanobots.
//! * There are no cubes that are closer.
//! * The coordinates cannot be refined any further.
//!
//! [`MinHeap`]: crate::util::heap
use crate::util::heap::*;
use crate::util::iter::*;
use crate::util::parse::*;

pub struct Nanobot {
    x: i32,
    y: i32,
    z: i32,
    r: i32,
}

impl Nanobot {
    fn from([x, y, z, r]: [i32; 4]) -> Nanobot {
        Nanobot { x, y, z, r }
    }

    fn manhattan(&self, other: &Nanobot) -> i32 {
        (self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
    }
}

struct Cube {
    x1: i32,
    x2: i32,
    y1: i32,
    y2: i32,
    z1: i32,
    z2: i32,
}

impl Cube {
    fn new(x1: i32, x2: i32, y1: i32, y2: i32, z1: i32, z2: i32) -> Cube {
        Cube { x1, x2, y1, y2, z1, z2 }
    }

    /// Split the cube into 8 non-overlapping sub-cubes.
    /// Since each cube size is always of power of two, we can safely divide by 2.
    fn split(&self) -> [Cube; 8] {
        let Cube { x1, x2, y1, y2, z1, z2 } = *self;

        // Lower and upper halves of the new sub-cubes.
        let lx = (self.x1 + self.x2) / 2;
        let ly = (self.y1 + self.y2) / 2;
        let lz = (self.z1 + self.z2) / 2;
        let ux = lx + 1;
        let uy = ly + 1;
        let uz = lz + 1;

        // 8 possible permutations of lower and upper halves for each axis.
        [
            Cube::new(x1, lx, y1, ly, z1, lz),
            Cube::new(ux, x2, y1, ly, z1, lz),
            Cube::new(x1, lx, uy, y2, z1, lz),
            Cube::new(ux, x2, uy, y2, z1, lz),
            Cube::new(x1, lx, y1, ly, uz, z2),
            Cube::new(ux, x2, y1, ly, uz, z2),
            Cube::new(x1, lx, uy, y2, uz, z2),
            Cube::new(ux, x2, uy, y2, uz, z2),
        ]
    }

    // Compute the Manattan distance from the faces of the cube to the octohedron shaped region
    // within range of the Nanbot.
    fn in_range(&self, nb: &Nanobot) -> bool {
        let x = (self.x1 - nb.x).max(0) + (nb.x - self.x2).max(0);
        let y = (self.y1 - nb.y).max(0) + (nb.y - self.y2).max(0);
        let z = (self.z1 - nb.z).max(0) + (nb.z - self.z2).max(0);
        x + y + z <= nb.r
    }

    /// Find the corner closest to the origin, considering each axis independently.
    fn closest(&self) -> i32 {
        let x = self.x1.abs().min(self.x2.abs());
        let y = self.y1.abs().min(self.y2.abs());
        let z = self.z1.abs().min(self.z2.abs());
        x + y + z
    }

    /// All axes are the same same so choose `x` arbitrarily.
    fn size(&self) -> i32 {
        self.x2 - self.x1 + 1
    }
}

pub fn parse(input: &str) -> Vec<Nanobot> {
    input.iter_signed().chunk::<4>().map(Nanobot::from).collect()
}

pub fn part1(input: &[Nanobot]) -> usize {
    let strongest = input.iter().max_by_key(|nb| nb.r).unwrap();
    input.iter().filter(|nb| strongest.manhattan(nb) <= strongest.r).count()
}

pub fn part2(input: &[Nanobot]) -> i32 {
    // Start with a single cube that encloses all nanobots. Cubes faces are aligned to powers of 2,
    // for example 0..4, 8..16, -32..0
    const SIZE: i32 = 1 << 29;
    let mut heap = MinHeap::with_capacity(1_000);
    heap.push((0, 0, 0), Cube::new(-SIZE, SIZE - 1, -SIZE, SIZE - 1, -SIZE, SIZE - 1));

    while let Some((_, cube)) = heap.pop() {
        if cube.size() == 1 {
            return cube.closest();
        }

        for next in cube.split() {
            let in_range = input.iter().filter(|nb| next.in_range(nb)).count();
            let key = (input.len() - in_range, next.closest(), next.size());
            heap.push(key, next);
        }
    }

    unreachable!()
}