#include #include #include #include #include #include #include #include #include #include #include #include namespace { using Recipes = std::map>>>; auto Parse(std::istream & in) -> Recipes { Recipes result; std::string line; std::int64_t n; std::string word; while (std::getline(in, line)) { std::vector> components; std::istringstream sin {line}; bool leftSide; do { sin >> n >> word; leftSide = word.back() == ','; if (leftSide) word.pop_back(); components.push_back({n, word}); } while(leftSide); sin >> word; // skip sin >> n >> word; result.insert({word, {n, std::move(components)}}); } return result; } auto Topsort(Recipes const& recipes) -> std::vector { // Kahn's algorithm std::vector result; result.reserve(recipes.size()); std::map marks; // false temp, true done auto const visit = [&](auto const& visit, auto const& name) -> void { auto [mark_iterator, is_new] = marks.try_emplace(name, false); auto& [_, is_done] = *mark_iterator; if (is_new) { auto const rit = recipes.find(name); if (rit != recipes.end()) { for (auto const& [_, c] : rit->second.second) { visit(visit, c); } } is_done = true; result.push_back(name); } else if (!is_done) { throw std::runtime_error{"cyclic graph"}; } }; for (auto const& [c,_] : recipes) { visit(visit, c); } std::reverse(result.begin(), result.end()); return result; } class Machine { struct Entry { std::int64_t need; std::int64_t batch; std::vector> components; }; std::int64_t ore_; std::vector process_; public: Machine(Recipes const& recipes) { auto order = Topsort(recipes); order.pop_back(); process_.resize(order.size(), {}); std::map indexes; for (auto && name : order) { indexes.insert({name, indexes.size()}); } for (auto && [k,v] : recipes) { auto & entry = process_[indexes[k]]; entry.batch = v.first; for (auto && [m,c] : v.second) { entry.components.push_back({m, c == "ORE" ? ore_ : process_[indexes[c]].need}); } } } auto operator()(std::int64_t fuel) { for (auto && entry : process_) { entry.need = 0; } process_[0].need = fuel; ore_ = 0; for (auto && entry : process_) { auto batches = (entry.need + entry.batch - 1) / entry.batch; for (auto && [m,c] : entry.components) { c += batches*m; } } return ore_; } }; auto ComputeFuel(Machine & machine, std::int64_t ore) { std::int64_t tooHi = 1; while (machine(tooHi) <= ore) { tooHi *= 2; } std::int64_t lo = 0; while (lo+1 != tooHi) { auto mid = std::midpoint(lo, tooHi); (machine(mid) > ore ? tooHi : lo) = mid; } return lo; } } // namespace TEST_SUITE("documented examples") { TEST_CASE("a") { std::istringstream in { "10 ORE => 10 A\n" "1 ORE => 1 B\n" "7 A, 1 B => 1 C\n" "7 A, 1 C => 1 D\n" "7 A, 1 D => 1 E\n" "7 A, 1 E => 1 FUEL\n"}; Machine m{Parse(in)}; REQUIRE(m(1) == 31); } TEST_CASE("b") { std::istringstream in { "9 ORE => 2 A\n" "8 ORE => 3 B\n" "7 ORE => 5 C\n" "3 A, 4 B => 1 AB\n" "5 B, 7 C => 1 BC\n" "4 C, 1 A => 1 CA\n" "2 AB, 3 BC, 4 CA => 1 FUEL\n"}; Machine m{Parse(in)}; REQUIRE(m(1) == 165); } TEST_CASE("c") { std::istringstream in { "157 ORE => 5 NZVS\n" "165 ORE => 6 DCFZ\n" "44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL\n" "12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ\n" "179 ORE => 7 PSHF\n" "177 ORE => 5 HKGWZ\n" "7 DCFZ, 7 PSHF => 2 XJWVT\n" "165 ORE => 2 GPVTF\n" "3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT\n"}; Machine m{Parse(in)}; REQUIRE(m(1) == 13312); REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 82892753); } TEST_CASE("d") { std::istringstream in { "2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG\n" "17 NVRVD, 3 JNWZP => 8 VPVL\n" "53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL\n" "22 VJHF, 37 MNCFX => 5 FWMGM\n" "139 ORE => 4 NVRVD\n" "144 ORE => 7 JNWZP\n" "5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC\n" "5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV\n" "145 ORE => 6 MNCFX\n" "1 NVRVD => 8 CXFTF\n" "1 VJHF, 6 MNCFX => 4 RFSQX\n" "176 ORE => 6 VJHF\n"}; Machine m{Parse(in)}; REQUIRE(m(1) == 180697); REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 5586022); } TEST_CASE("e") { std::istringstream in { "171 ORE => 8 CNZTR\n" "7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL\n" "114 ORE => 4 BHXH\n" "14 VRPVC => 6 BMBT\n" "6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL\n" "6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT\n" "15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW\n" "13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW\n" "5 BMBT => 4 WPTQ\n" "189 ORE => 9 KTJDG\n" "1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP\n" "12 VRPVC, 27 CNZTR => 2 XDBXC\n" "15 KTJDG, 12 BHXH => 5 XCVML\n" "3 BHXH, 2 VRPVC => 7 MZWV\n" "121 ORE => 7 VRPVC\n" "7 XCVML => 6 RJRHP\n" "5 BHXH, 4 VRPVC => 5 LTCX\n"}; Machine m{Parse(in)}; REQUIRE(m(1) == 2210736); REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 460664); } } auto main(int argc, char** argv) -> int { auto recipes = Parse(aocpp::Startup(argc, argv)); auto machine = Machine(recipes); auto part1 = machine(1); auto part2 = ComputeFuel(machine, 1'000'000'000'000); std::cout << "Part 1: " << part1 << std::endl; std::cout << "Part 2: " << part2 << std::endl; }