|
1 | 1 | //! # Air Duct Spelunking |
2 | 2 | //! |
3 | | -//! This is a variant of the classic |
4 | | -//! [Travelling Salesman Problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) and |
5 | | -//! is similar to [`Year 2015 Day 13`]. |
| 3 | +//! This is a variant of the classic [Travelling Salesman Problem] and is similar to |
| 4 | +//! [`Year 2015 Day 13`]. |
6 | 5 | //! |
7 | 6 | //! We first simplify the problem by finding the distance between all locations using multiple |
8 | 7 | //! [BFS](https://en.wikipedia.org/wiki/Breadth-first_search) |
9 | 8 | //! searches starting from each location. |
10 | 9 | //! |
11 | | -//! For speed we convert each location into an index, then store the distances between |
12 | | -//! every pair of locations in a vec for fast lookup. Our utility [`half_permutations`] method uses |
13 | | -//! [Steinhaus-Johnson-Trotter's algorithm](https://en.wikipedia.org/wiki/Steinhaus-Johnson-Trotter_algorithm) for efficiency, |
14 | | -//! modifying the slice in place. |
| 10 | +//! Then we can use [Held-Karp's dynamic programming][Held-Karp] algorithm to determine the |
| 11 | +//! shortest cycle. The problem asks us to start from node 0, which conveniently means that the |
| 12 | +//! value g(127, k) is the shortest path to k, and adding the distance from k back to 0 for part 2 |
| 13 | +//! is also trivial. Thus, this day completes with only `7*6/2*128/2` or 1,344 comparisons, quite a |
| 14 | +//! bit better than the 2,520 comparisons needed for an approach with 7!/2 permutations. A |
| 15 | +//! slight complication is that set bit 0 maps to node 1. |
15 | 16 | //! |
16 | | -//! There are 8 locations, however since we always start at `0` this requires checking only |
17 | | -//! 7!/2 = 2,520 permutations. We find the answer to both part one and two simultaneously. |
18 | | -//! |
19 | | -//! [`half_permutations`]: crate::util::slice |
20 | 17 | //! [`Year 2015 Day 13`]: crate::year2015::day13 |
| 18 | +//! [Travelling Salesman Problem]: https://en.wikipedia.org/wiki/Travelling_salesman_problem |
| 19 | +//! [Held-Karp]: https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm |
| 20 | +use crate::util::bitset::*; |
21 | 21 | use crate::util::grid::*; |
22 | 22 | use crate::util::parse::*; |
23 | | -use crate::util::slice::*; |
24 | 23 | use std::collections::VecDeque; |
25 | 24 |
|
26 | | -type Input = (u32, u32); |
| 25 | +type Input = (u16, u16); |
27 | 26 |
|
28 | 27 | pub fn parse(input: &str) -> Input { |
29 | 28 | let grid = Grid::parse(input); |
@@ -77,26 +76,52 @@ pub fn parse(input: &str) -> Input { |
77 | 76 | } |
78 | 77 |
|
79 | 78 | // Solve both parts simultaneously. |
80 | | - let mut part_one = u32::MAX; |
81 | | - let mut part_two = u32::MAX; |
82 | | - let mut indices: Vec<_> = (1..found.len()).collect(); |
| 79 | + // Initialize a table for each part: 2ⁿ⁻¹ sets with n-1 distances per set. Default each g({k},k) |
| 80 | + // singleton to distance[0][k+1] (since bit 0 maps to node 1), while the initial value of other |
| 81 | + // sets does not matter. |
| 82 | + let mut table = [[0_u16; 7]; 1 << 7]; |
| 83 | + for k in 0..found.len() - 1 { |
| 84 | + table[1 << k][k] = distance[0][k + 1]; |
| 85 | + } |
83 | 86 |
|
84 | | - indices.half_permutations(|slice| { |
85 | | - let first = distance[0][slice[0]]; |
86 | | - let middle = slice.array_windows().map(|&[from, to]| distance[from][to]).sum::<u32>(); |
87 | | - let last = distance[slice[slice.len() - 1]][0]; |
| 87 | + // Visit each non-empty set in order, with no work to do for singleton sets. |
| 88 | + for set in 3_usize..(1 << (found.len() - 1)) { |
| 89 | + if set & !(set - 1) == set { |
| 90 | + continue; |
| 91 | + } |
88 | 92 |
|
89 | | - part_one = part_one.min(first + middle).min(middle + last); |
90 | | - part_two = part_two.min(first + middle + last); |
91 | | - }); |
| 93 | + // For a given set, compute each g(set,k) for all k in the set. |
| 94 | + for k in set.biterator() { |
| 95 | + let subset = set ^ (1 << k); |
| 96 | + let mut shortest = u16::MAX; |
| 97 | + |
| 98 | + // For a given destination k, find which other bit m gives the best path from the |
| 99 | + // subset to m, and then m to k. All table[subset] references were filled in prior |
| 100 | + // iterations of the outer loop or the singleton base cases. |
| 101 | + for m in subset.biterator() { |
| 102 | + shortest = shortest.min(table[subset][m] + distance[m + 1][k + 1]); |
| 103 | + } |
| 104 | + table[set][k] = shortest; |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + // With the sets now built, we have 7 candidates for each answer. |
| 109 | + let mut part_one = u16::MAX; |
| 110 | + let mut part_two = u16::MAX; |
| 111 | + for (k, &path_len) in |
| 112 | + table[(1 << (found.len() - 1)) - 1].iter().take(found.len() - 1).enumerate() |
| 113 | + { |
| 114 | + part_one = part_one.min(path_len); |
| 115 | + part_two = part_two.min(path_len + distance[k + 1][0]); |
| 116 | + } |
92 | 117 |
|
93 | 118 | (part_one, part_two) |
94 | 119 | } |
95 | 120 |
|
96 | | -pub fn part1(input: &Input) -> u32 { |
| 121 | +pub fn part1(input: &Input) -> u16 { |
97 | 122 | input.0 |
98 | 123 | } |
99 | 124 |
|
100 | | -pub fn part2(input: &Input) -> u32 { |
| 125 | +pub fn part2(input: &Input) -> u16 { |
101 | 126 | input.1 |
102 | 127 | } |
0 commit comments