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 133
//! # Electromagnetic Moat
//!
//! Both parts are calculated at the same time by recurively building all possible bridge
//! combinations. Two optimizations are used to speed things up ten times
//!
//! First ports that only appear in two components are merged. For example `2/17` and `17/3`
//! becomes a single component `2/3` with a weight of 39 and a length of 2. This shaves about 30%
//! off the time needed.
//!
//! The second optimization is far more critical and reduces the time needed by 85%. The
//! observation is that compenets with two ports the same, for example `7/7` are always optimal
//! to pick first, as they increase strength and length without changing the port number.
//!
//! If we can place such a component then there's no need to consider further components which
//! reduces the total number of combination to consider.
use crate::util::bitset::*;
use crate::util::iter::*;
use crate::util::parse::*;
struct Component {
left: usize,
right: usize,
weight: usize,
length: usize,
}
struct State {
possible: [usize; 64],
both: [usize; 64],
weight: [usize; 64],
length: [usize; 64],
bridge: [usize; 64],
}
pub fn parse(input: &str) -> [usize; 64] {
let mut components: Vec<_> = input
.iter_unsigned()
.chunk::<2>()
.map(|[left, right]| Component { left, right, weight: left + right, length: 1 })
.collect();
// First optimization. If a port value appears in only 2 components (excluding zero)
// then fuse the components together.
let mut indices = Vec::new();
for n in 1..64 {
for (index, component) in components.iter().enumerate() {
if component.left == n || component.right == n {
indices.push(index);
}
}
if indices.len() == 2 {
let second = components.swap_remove(indices[1]);
let first = components.swap_remove(indices[0]);
let left = if first.left == n { first.right } else { first.left };
let right = if second.left == n { second.right } else { second.left };
let weight = first.weight + second.weight;
let length = first.length + second.length;
components.push(Component { left, right, weight, length });
}
indices.clear();
}
// Second optimization. Sort components with both ports the same before other components,
// so that the loop when choosing componentns in `build` function can terminate early.
components.sort_unstable_by(|a, b| {
(a.left ^ a.right).cmp(&(b.left ^ b.right)).then(a.left.cmp(&b.left))
});
let mut state = State {
possible: [0; 64],
both: [0; 64],
weight: [0; 64],
length: [0; 64],
bridge: [0; 64],
};
for (index, component) in components.iter().enumerate() {
let mask = 1 << index;
state.possible[component.left] |= mask;
state.possible[component.right] |= mask;
// Bitwise logic trick. `a ^ b ^ a = b` and `a ^ b ^ b = a` so given a single port and
// the XOR of both we can work out the other port of a component.
state.both[index] = component.left ^ component.right;
state.weight[index] = component.weight;
state.length[index] = component.length;
}
// Recursively build all possible bridges.
build(&mut state, 0, 0, 0, 0);
state.bridge
}
/// Strongest bridge.
pub fn part1(input: &[usize]) -> usize {
*input.iter().max().unwrap()
}
/// Longest bridge.
pub fn part2(input: &[usize]) -> usize {
*input.iter().rfind(|&&n| n > 0).unwrap()
}
fn build(state: &mut State, current: usize, used: usize, strength: usize, length: usize) {
// Bitset of all unused components that have a matching port.
let remaining = state.possible[current] & !used;
// Extract the index of each component from the bitset.
for index in remaining.biterator() {
let next = current ^ state.both[index];
let used = used | (1 << index);
let strength = strength + state.weight[index];
let length = length + state.length[index];
if state.possible[next] & !used == 0 {
// No more possible components to add to the bridge.
state.bridge[length] = state.bridge[length].max(strength);
} else {
build(state, next, used, strength, length);
// Critical optimization. If this is a component with two ports of the same values,
// for example 5/5 or 7/7 then it's always optimal to add it to the bridge.
// We don't need to consider further options.
if current == next {
break;
}
}
}
}