aocpp/2019/14.cpp

151 lines
3.4 KiB
C++
Raw Normal View History

2022-11-08 21:28:12 -08:00
#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::vector<std::string> result;
2022-11-09 15:45:55 -08:00
result.reserve(recipes.size());
std::map<std::string, bool> 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);
2022-11-08 21:28:12 -08:00
}
}
2022-11-09 15:45:55 -08:00
is_done = true;
2022-11-08 21:28:12 -08:00
result.push_back(name);
2022-11-09 15:45:55 -08:00
} else if (!is_done) {
throw std::runtime_error{"cyclic graph"};
2022-11-08 21:28:12 -08:00
}
};
2022-11-09 15:45:55 -08:00
for (auto const& [c,_] : recipes) {
visit(visit, c);
2022-11-08 21:28:12 -08:00
}
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;
}