Skip to main content

aoc/year2015/
day13.rs

1//! # Knights of the Dinner Table
2//!
3//! This problem is very similar to [`Day 9`] and we solve it in almost exactly the same way by
4//! computing an adjacency matrix of happiness then running [Held-Karp] to find the longest
5//! cycle. If part one were the only problem at hand, the answer would be possible by iterating
6//! over 127 sets and then selecting among seven candidates to close the loop back to whichever
7//! abritrary point we pinned as the start.
8//!
9//! However, we are more interested in solving part two at the same time. Do this by noticing that
10//! when you insert yourself between two diners, you set the value of their mutual link to zero.
11//! This is effectively the same as inserting a ninth node into the algorithm, which we pin as the
12//! start node before iterating over 255 sets. Meanwhile, the results for part one can still be
13//! found from the table, if we also have an easy way to determine which diner was used to start
14//! the path represented by any given g(set,k). We can then manually close the loop of 8 diners
15//! by using all 8 g(255,k) plus the distance from k to the start node of that path, while the
16//! loop of 9 diners uses g(255,k) with no additional distance.
17//!
18//! [`Day 9`]: crate::year2015::day09
19//! [Held-Karp]: https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm
20use crate::util::bitset::*;
21use crate::util::hash::*;
22use crate::util::iter::*;
23use crate::util::parse::*;
24
25type Input = (i16, i16);
26
27pub fn parse(input: &str) -> Input {
28    // Assign each diner an index on a first come first served basis.
29    let tokens: Vec<_> = input.split([' ', '.', '\n']).chunk::<12>().collect();
30    let mut indices = FastMap::new();
31
32    for &[from, .., to, _] in &tokens {
33        for key in [from, to] {
34            let size = indices.len();
35            indices.entry(key).or_insert(size);
36        }
37    }
38
39    // Calculate the happiness values. Note that the values are not reciprocal a => b != b => a.
40    let stride = indices.len();
41    let mut happiness = vec![0_i16; stride * stride];
42
43    for &[from, _, gain_lose, value, .., to, _] in &tokens {
44        let start = indices[from];
45        let end = indices[to];
46        let sign = if gain_lose == "gain" { 1 } else { -1 };
47        let value: i16 = value.signed();
48
49        // Add the values together to make the mutual link reciprocal.
50        happiness[stride * start + end] += sign * value;
51        happiness[stride * end + start] += sign * value;
52    }
53
54    // Solve both parts simultaneously.
55    // Initialize a shared table for both parts: 2ⁿ sets with n distances per set. Default 0 matches
56    // g({k},k) for all singleton sets of zero distance from yourself, but tracking k as the start
57    // of the path. The initial value of other sets does not matter.
58    let zero = (0_i16, 0_u8);
59    let mut table = vec![zero; stride * (1 << stride)];
60    for k in 0..stride {
61        table[(1 << k) * stride + k].1 = k as u8;
62    }
63
64    // Visit each non-empty set in order, with no work to do for singleton sets. Start from 3,
65    // since 1 and 2 are singleton sets.
66    for set in 3_usize..(1 << stride) {
67        if set.is_power_of_two() {
68            continue;
69        }
70
71        // For a given set, compute each g(set,k) for all k in the set.
72        for k in set.biterator() {
73            let subset = set ^ (1 << k);
74            let mut longest = i16::MIN;
75            let mut start = u8::MAX;
76
77            // For a given destination k, find which other bit m gives the best path from the
78            // subset to m, and then m to k. All table[subset] references were filled in prior
79            // iterations of the outer loop or the singleton base cases.
80            for m in subset.biterator() {
81                let prior = table[subset * stride + m];
82                let distance = prior.0 + happiness[m * stride + k];
83                if distance > longest {
84                    longest = distance;
85                    start = prior.1;
86                }
87            }
88            table[set * stride + k] = (longest, start);
89        }
90    }
91
92    // With the sets now built, we have stride candidates for each answer.
93    // Part one requires completing the cycle back to the stashed first element.
94    // Part two can be directly read off the table.
95    let mut part_one = i16::MIN;
96    let mut part_two = i16::MIN;
97    for (k, &prior) in table[table.len() - stride..].iter().enumerate() {
98        part_one = part_one.max(prior.0 + happiness[prior.1 as usize * stride + k]);
99        part_two = part_two.max(prior.0);
100    }
101
102    (part_one, part_two)
103}
104
105pub fn part1(input: &Input) -> i16 {
106    input.0
107}
108
109pub fn part2(input: &Input) -> i16 {
110    input.1
111}