Skip to main content

aoc/year2015/
day09.rs

1//! # All in a Single Night
2//!
3//! This is a variant of the classic NP-hard [Travelling Salesman Problem].
4//!
5//! There are 8 locations, so naively it would require checking 8! = 40,320 permutations. We can
6//! reduce this to 7!/2 = 2,520 permutations by arbitrarily choosing one of the locations as the
7//! start, and skipping lexically reversed permutations (since the path a->b->c has the same
8//! length as c->b->a). Computing the shortest and longest path is then done by completing the
9//! cycle for each permutation, then discarding the longest or shortest edge seen along the way.
10//! Skipping lexically reversed permutations is possible with
11//! [Steinhaus-Johnson-Trotter][Steinhaus-Johnson-Trotter's algorithm].
12//!
13//! However, since the graph is complete (every node has a distance to every other node), this
14//! particular problem can be solved even faster, by avoiding the overhead of permutations and
15//! instead using [Held-Karp's dynamic programming][Held-Karp] solution. This algorithm is
16//! O(n²*2ⁿ). For our puzzle with 8 nodes, this gives `8*7/2*256/2` or 3,584 comparisons needed.
17//! On the surface, this is more comparisons than the 2,520 sequences visited by the O(n!)
18//! permutation solution, but set manipulation is less expensive than computation of permutations
19//! followed by casting out the longest or shortest edge, so it is an overall win.
20//!
21//! The core behavior of Held-Karp involves computing the function g(set, k) for all possible
22//! sets of cities, where the function represents the best (shortest or longest) distance seen so
23//! far for a given set of cities and ending on the city k which is a member of the set. Unlike the
24//! permutations approach which visits every path in the graph, Held-Karp discards information
25//! along the way to compute only the best cycle in a graph anchored to a given start point. It is
26//! easy to demonstrate a graph where the best path is not part of the best cycle (discarding the
27//! longest edge from the shortest cycle might leave you with a path longer than the graph's true
28//! shortest path, if that other path had a different start anchor). But this is easy to work
29//! around, by adding a ninth "location" with distance 0 to every other location, and using that
30//! location as the start and end for every cycle, at which point the best cycle of nine includes
31//! the best path of all eight locations.
32//!
33//! The algorithm is recursive: with a base case of g({k},k) being zero (the best distance
34//! to a singleton set from our ninth point is 0), all other g(set,k) can be computed by
35//! iterating over each member of set excluding k, and choosing the best variant g(set∖k,m)+d(k,m)
36//! possible from a smaller set ending in m. Iterating over bitmasks from 0 to 255 ensures
37//! that all earlier subsets are available when computing for a larger set.
38//!
39//! For speed we first convert each location into an index, then store the distances between
40//! every pair of locations in an array for fast lookup. Storing sets plus the last city visited
41//! requires 8 bits for the set and 3 bits for the city, for a total table size of 2¹¹ distances.
42//!
43//! [Travelling Salesman Problem]: https://en.wikipedia.org/wiki/Travelling_salesman_problem
44//! [Steinhaus-Johnson-Trotter]: https://en.wikipedia.org/wiki/Steinhaus-Johnson-Trotter_algorithm
45//! [Held-Karp]: https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm
46use crate::util::bitset::*;
47use crate::util::hash::*;
48use crate::util::iter::*;
49use crate::util::parse::*;
50
51type Result = (u16, u16);
52
53pub fn parse(input: &str) -> Result {
54    let tokens: Vec<_> = input.split_ascii_whitespace().chunk::<5>().collect();
55    let mut indices = FastMap::new();
56
57    for [start, _, end, ..] in &tokens {
58        for key in [start, end] {
59            let size = indices.len();
60            indices.entry(key).or_insert(size);
61        }
62    }
63
64    let stride = indices.len();
65    let mut distances = vec![0_u16; stride * stride];
66
67    for [start, _, end, _, distance] in &tokens {
68        let start = indices[start];
69        let end = indices[end];
70        let distance = distance.unsigned();
71
72        distances[stride * start + end] = distance;
73        distances[stride * end + start] = distance;
74    }
75
76    // Initialize a table for each part: 2ⁿ sets with n distances per set. Default 0 matches
77    // g({k},k) of zero for all singleton sets. Initial value of other sets does not matter.
78    let mut table_one = vec![0_u16; stride * (1 << stride)];
79    let mut table_two = vec![0_u16; stride * (1 << stride)];
80
81    // Visit each non-empty set in order, with no work to do for singleton sets. Start from 3,
82    // since 1 and 2 are singleton sets.
83    for set in 3_usize..(1 << stride) {
84        if set.is_power_of_two() {
85            continue;
86        }
87
88        // For a given set, compute each g(set,k) for all k in the set.
89        for k in set.biterator() {
90            let subset = set ^ (1 << k);
91            let mut shortest = u16::MAX;
92            let mut longest = 0;
93
94            // For a given destination k, find which other bit m gives the best path from the
95            // subset to m, and then m to k. All table[subset] references were filled in prior
96            // iterations of the outer loop or the singleton base cases.
97            for m in subset.biterator() {
98                shortest = shortest.min(table_one[subset * stride + m] + distances[m * stride + k]);
99                longest = longest.max(table_two[subset * stride + m] + distances[m * stride + k]);
100            }
101            table_one[set * stride + k] = shortest;
102            table_two[set * stride + k] = longest;
103        }
104    }
105
106    // With the sets now built, we have stride candidates for each answer.
107    let last_row = table_one.len() - stride;
108    (*table_one[last_row..].iter().min().unwrap(), *table_two[last_row..].iter().max().unwrap())
109}
110
111pub fn part1(input: &Result) -> u16 {
112    input.0
113}
114
115pub fn part2(input: &Result) -> u16 {
116    input.1
117}