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
//! # No Such Thing as Too Much
//!
//! Given `n` items the number of possible subsets is `2ⁿ`. We could brute force through each
//! subset by iterating from 0 to 2ⁿ using the binary bits to indicate if a container is present.
//! This will work but is a little slow as there are 20 containers, giving 2²⁰ = 1048576
//! combinations to check.
//!
//! We speed things up by noticing that some containers are the same size, so the total number
//! of combinations is fewer. For example if there are 3 containers of size `x` that is only
//! 4 possibilities rather than 8.
//!
//! We have to multiply the result by [nCr](https://en.wikipedia.org/wiki/Combination)
//! or the number of ways of choosing `r` items from `n`. For example if 2 containers out of 3
//! are used in a solution then there are 3 ways of selecting the 2 containers. This solution
//! hardcodes nCr tables for `n` up to 4.
//!
//! As an optimization containers are ordered from largest to smallest so that the
//! recursive checking can exit as early as possible if we exceed 150 litres.
use crate::util::parse::*;
use std::collections::BTreeMap;
/// nCr for `n` from 0 to 4 inclusive.
const NCR: [[u32; 5]; 5] =
[[1, 0, 0, 0, 0], [1, 1, 0, 0, 0], [1, 2, 1, 0, 0], [1, 3, 3, 1, 0], [1, 4, 6, 4, 1]];
struct State {
size: Vec<u32>,
freq: Vec<usize>,
result: Vec<u32>,
}
pub fn parse(input: &str) -> Vec<u32> {
// Collect size and frequency of each container.
let mut containers = BTreeMap::new();
for size in input.iter_unsigned() {
containers.entry(size).and_modify(|e| *e += 1).or_insert(1);
}
// Convenience struct to group size and frequency of each container, plus the number of
// combinations grouped by total number of containers.
let mut state = State {
size: containers.keys().copied().collect(),
freq: containers.values().copied().collect(),
result: vec![0; containers.len()],
};
// As an optimization order largest containers first so that we can exit early if the total
// size is greater than 150.
state.size.reverse();
state.freq.reverse();
combinations(&mut state, 0, 0, 0, 1);
state.result
}
/// We only care about the total combinations, so sum the entire vec.
pub fn part1(input: &[u32]) -> u32 {
input.iter().sum()
}
/// We want the number of combination with the fewest containers, so find first non-zero value.
pub fn part2(input: &[u32]) -> u32 {
*input.iter().find(|&&n| n > 0).unwrap()
}
/// Recursively try every possible combination, returning early if the size exceeds 150 litres.
///
/// `state`: Convenience struct to reduce parameters
/// `index`: Current container
/// `containers`: Number of containers used so far
/// `litres`: How many litres of eggnog stored so far
/// `factor`: The total different number of ways of selecting previous containers
fn combinations(state: &mut State, index: usize, containers: usize, litres: u32, factor: u32) {
let n = state.freq[index];
let mut next = litres;
for r in 0..(n + 1) {
if next < 150 {
if index < state.size.len() - 1 {
combinations(state, index + 1, containers + r, next, factor * NCR[n][r]);
}
} else {
if next == 150 {
state.result[containers + r] += factor * NCR[n][r];
}
break;
}
next += state.size[index];
}
}