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//! [`half_permutations`]: crate::util::slice
45//! [Steinhaus-Johnson-Trotter]: https://en.wikipedia.org/wiki/Steinhaus-Johnson-Trotter_algorithm
46//! [Held-Karp]: https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm
47use crate::util::bitset::*;
48use crate::util::hash::*;
49use crate::util::iter::*;
50use crate::util::parse::*;
51
52type Result = (u16, u16);
53
54pub fn parse(input: &str) -> Result {
55    let tokens: Vec<_> = input.split_ascii_whitespace().chunk::<5>().collect();
56    let mut indices = FastMap::new();
57
58    for [start, _, end, ..] in &tokens {
59        for key in [start, end] {
60            let size = indices.len();
61            indices.entry(key).or_insert(size);
62        }
63    }
64
65    let stride = indices.len();
66    let mut distances = vec![0_u16; stride * stride];
67
68    for [start, _, end, _, distance] in &tokens {
69        let start = indices[start];
70        let end = indices[end];
71        let distance = distance.unsigned();
72
73        distances[stride * start + end] = distance;
74        distances[stride * end + start] = distance;
75    }
76
77    // Initialize a table for each part: 2ⁿ sets with n distances per set. Default 0 matches
78    // g({k},k) of zero for all singleton sets. Initial value of other sets does not matter.
79    let mut table_one = vec![0_u16; stride * (1 << stride)];
80    let mut table_two = vec![0_u16; stride * (1 << stride)];
81
82    // Visit each non-empty set in order, with no work to do for singleton sets. Start from 3,
83    // since 1 and 2 are singleton sets.
84    for set in 3_usize..(1 << stride) {
85        if set.is_power_of_two() {
86            continue;
87        }
88
89        // For a given set, compute each g(set,k) for all k in the set.
90        for k in set.biterator() {
91            let subset = set ^ (1 << k);
92            let mut shortest = u16::MAX;
93            let mut longest = 0;
94
95            // For a given destination k, find which other bit m gives the best path from the
96            // subset to m, and then m to k. All table[subset] references were filled in prior
97            // iterations of the outer loop or the singleton base cases.
98            for m in subset.biterator() {
99                shortest = shortest.min(table_one[subset * stride + m] + distances[m * stride + k]);
100                longest = longest.max(table_two[subset * stride + m] + distances[m * stride + k]);
101            }
102            table_one[set * stride + k] = shortest;
103            table_two[set * stride + k] = longest;
104        }
105    }
106
107    // With the sets now built, we have stride candidates for each answer.
108    let last_row = table_one.len() - stride;
109    (*table_one[last_row..].iter().min().unwrap(), *table_two[last_row..].iter().max().unwrap())
110}
111
112pub fn part1(input: &Result) -> u16 {
113    input.0
114}
115
116pub fn part2(input: &Result) -> u16 {
117    input.1
118}