148 lines
3.3 KiB
C++
148 lines
3.3 KiB
C++
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iterator>
|
|
#include <numeric>
|
|
#include <map>
|
|
#include <set>
|
|
#include <vector>
|
|
#include <tuple>
|
|
|
|
#include <aocpp/Startup.hpp>
|
|
|
|
namespace {
|
|
|
|
using Recipes = std::map<std::string, std::pair<std::int64_t, std::vector<std::pair<std::int64_t, std::string>>>>;
|
|
|
|
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<std::pair<std::int64_t, std::string>> 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<std::string>
|
|
{
|
|
// Kahn's algorithm
|
|
std::set<std::string> perm;
|
|
std::set<std::string> temp;
|
|
std::vector<std::string> result;
|
|
|
|
auto visit_ = [&](auto& rec, std::string name) -> void {
|
|
if (!perm.contains(name)) {
|
|
if (!temp.insert(name).second) throw std::runtime_error{"cyclic recipes"};
|
|
if (recipes.contains(name)) {
|
|
for (auto && [_, c] : recipes.at(name).second) {
|
|
rec(rec, c);
|
|
}
|
|
}
|
|
temp.erase(name);
|
|
perm.insert(name);
|
|
result.push_back(name);
|
|
}
|
|
};
|
|
auto visit = [&](auto name) { visit_(visit_, name); };
|
|
|
|
for (auto && [k,_] : recipes) {
|
|
visit(k);
|
|
}
|
|
|
|
std::reverse(result.begin(), result.end());
|
|
|
|
return result;
|
|
}
|
|
|
|
class Machine {
|
|
struct Entry {
|
|
std::int64_t need;
|
|
std::int64_t batch;
|
|
std::vector<std::pair<std::int64_t, std::int64_t&>> components;
|
|
};
|
|
std::int64_t ore_;
|
|
std::vector<Entry> process_;
|
|
public:
|
|
Machine(Recipes const& recipes)
|
|
{
|
|
auto order = Topsort(recipes);
|
|
order.pop_back();
|
|
process_.resize(order.size(), {});
|
|
|
|
std::map<std::string, std::size_t> 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
|
|
|
|
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;
|
|
}
|