This commit is contained in:
Eric Mertens 2023-01-28 14:46:00 -08:00
parent b92a50d546
commit 6dac2534ab

View File

@ -6,10 +6,16 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <map> #include <map>
#include <unordered_map>
#include <limits>
#include <bitset> #include <bitset>
#include <boost/spirit/home/qi.hpp> #include <boost/spirit/home/qi.hpp>
#include <boost/range/irange.hpp> #include <boost/range/irange.hpp>
#include <boost/phoenix.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptor/map.hpp>
#include <doctest.h> #include <doctest.h>
@ -47,42 +53,50 @@ auto Parse(std::istream & in) -> std::vector<RawRoom>
std::vector<RawRoom> result; std::vector<RawRoom> result;
std::string line; std::string line;
while (std::getline(in, line)) { while (std::getline(in, line)) {
RawRoom room; namespace phx = boost::phoenix;
using namespace boost::spirit; namespace qi = boost::spirit::qi;
using namespace qi::labels;
using It = std::string::const_iterator;
auto const name = qi::as_string[+qi::alpha]; auto const name = qi::as_string[+qi::alpha];
auto const room_description =
qi::rule<It, RawRoom()> room_description =
"Valve " >> "Valve " >>
name [ ([&](auto n) { room.name = n; }) ] >> name [ phx::bind(&RawRoom::name, _val) = _1 ] >>
" has flow rate=" >> " has flow rate=" >>
qi::ulong_long [ ([&](auto f) { room.flow = f; }) ] >> qi::ulong_long [ phx::bind(&RawRoom::flow, _val) = _1 ] >>
"; tunnel" >> -qi::string("s") >> "; tunnel" >> -qi::string("s") >>
" lead" >> -qi::string("s") >> " lead" >> -qi::string("s") >>
" to valve" >> -qi::string("s") >> " to valve" >> -qi::string("s") >>
" " >> " " >>
(name % ", ") [ ([&](auto c) { room.connections = c; }) ]; (name % ", ") [ phx::bind(&RawRoom::connections, _val) = _1 ];
if (!qi::parse(line.begin(), line.end(), room_description)) { It b = line.begin();
It e = line.end();
result.emplace_back();
if (!qi::parse(b, e, room_description, result.back())) {
throw std::runtime_error{"bad input line"}; throw std::runtime_error{"bad input line"};
} }
result.emplace_back(std::move(room));
} }
return result; return result;
} }
auto GenerateGraph(std::vector<RawRoom> const& raw_rooms) -> std::vector<Room> auto GenerateGraph(std::vector<RawRoom> const& raw_rooms) -> std::pair<std::size_t, std::vector<Room>>
{ {
std::vector<Room> result; std::pair<std::size_t, std::vector<Room>> result {};
std::map<std::string, std::size_t> names; std::unordered_map<std::string, std::size_t> names;
std::vector<std::vector<std::uint64_t>> distances( std::vector<std::vector<std::uint64_t>> distances(
raw_rooms.size(), std::vector<std::uint64_t>(raw_rooms.size(), 100)); // todo max limit raw_rooms.size(), std::vector<std::uint64_t>(raw_rooms.size(), raw_rooms.size()));
for (auto const i : boost::irange(raw_rooms.size())) { for (auto const i : boost::irange(raw_rooms.size())) {
names[raw_rooms[i].name] = i; names[raw_rooms[i].name] = i;
if (raw_rooms[i].name == "AA") result.first = i;
} }
for (auto const i : boost::irange(raw_rooms.size())) { for (auto const i : boost::irange(raw_rooms.size())) {
auto & ds = distances[i]; auto & ds = distances[i];
ds[i] = 0;
for (auto const& name : raw_rooms[i].connections) { for (auto const& name : raw_rooms[i].connections) {
ds[names[name]] = 1; ds[names[name]] = 1;
} }
@ -91,7 +105,7 @@ auto GenerateGraph(std::vector<RawRoom> const& raw_rooms) -> std::vector<Room>
ShortestDistances(distances); ShortestDistances(distances);
for (auto const i : boost::irange(raw_rooms.size())) { for (auto const i : boost::irange(raw_rooms.size())) {
result.push_back({raw_rooms[i].flow, std::move(distances[i])}); result.second.push_back({raw_rooms[i].flow, std::move(distances[i])});
} }
return result; return result;
@ -106,37 +120,67 @@ struct State {
Valves valves; Valves valves;
}; };
auto Routes(std::vector<Room> const& rooms, std::uint64_t time) -> std::map<unsigned long long, std::uint64_t> auto Routes(
std::size_t const start,
std::vector<Room> const& rooms, std::uint64_t initial_time
) -> std::unordered_map<Valves, std::uint64_t>
{ {
std::vector<State> states { State{time, 0, 0, {}} }; std::vector<State> states { State{initial_time, 0, start, {}} };
std::map<unsigned long long, std::uint64_t> result; std::unordered_map<Valves, std::uint64_t> result;
while (!states.empty()) { while (!states.empty()) {
auto state = states.back(); auto const state = states.back();
states.pop_back(); states.pop_back();
auto const& room = rooms[state.location]; auto const& room = rooms[state.location];
for (auto const i : boost::irange(rooms.size())) { for (auto const i : boost::irange(rooms.size())) {
auto valves = state.valves; // don't revisit a valve
if (valves[i]) { continue; } if (state.valves.test(i)) { continue; }
auto const flow = rooms[i].flow;
if (flow == 0) { continue; }
auto const cost = room.connections[i];
if (cost >= state.time) { continue; }
auto const time_ = state.time - cost - 1;
valves[i] = true;
auto const flow_ = state.flow + time_;
if (result[valves.to_ullong()] < flow_) { // don't visit rooms with useless valves
result[valves.to_ullong()] = flow_; if (rooms[i].flow == 0) { continue; }
// don't visit rooms we can't get to in time
auto const cost = room.connections[i];
if (cost+1 >= state.time) { continue; }
auto const time = state.time - cost - 1;
auto const flow = state.flow + rooms[i].flow * time;
auto valves = state.valves;
valves.set(i);
// remember the best we've seen for this valve-set
if (result[valves] < flow) {
result[valves] = flow;
} }
states.push_back({ time_, state.flow + time_, i, valves}); states.push_back({time, flow, i, valves});
}
}
return result;
}
auto Part1(std::size_t start, std::vector<Room> const& rooms) -> std::uint64_t
{
auto const routes = Routes(start, rooms, 30);
return *boost::range::max_element(routes | boost::adaptors::map_values);
}
auto Part2(std::size_t start, std::vector<Room> const& rooms) -> std::uint64_t
{
auto const routes = Routes(start, rooms, 26);
auto const end = routes.end();
std::uint64_t result {0};
for (auto it1 = routes.begin(); it1 != end; std::advance(it1, 1)) {
for (auto it2 = std::next(it1); it2 != end; std::advance(it2, 1)) {
// only consider pairs that have disjoint sets of valves
if ((it1->first & it2->first).none()) {
result = std::max(result, it1->second + it2->second);
}
} }
} }
return result; return result;
} }
@ -145,22 +189,26 @@ auto Routes(std::vector<Room> const& rooms, std::uint64_t time) -> std::map<unsi
TEST_SUITE("2022-16 examples") { TEST_SUITE("2022-16 examples") {
TEST_CASE("example") { TEST_CASE("example") {
std::istringstream in { std::istringstream in {
R"(Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
}; Valve BB has flow rate=13; tunnels lead to valves CC, AA
Valve CC has flow rate=2; tunnels lead to valves DD, BB
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
Valve EE has flow rate=3; tunnels lead to valves FF, DD
Valve FF has flow rate=0; tunnels lead to valves EE, GG
Valve GG has flow rate=0; tunnels lead to valves FF, HH
Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II
)"};
auto const [start, graph] = GenerateGraph(Parse(in));
CHECK(1651 == Part1(start, graph));
CHECK(1707 == Part2(start, graph));
} }
} }
auto main(int argc, char** argv) -> int auto main(int argc, char** argv) -> int
{ {
auto const input = Parse(*aocpp::Startup(argc, argv)); auto const [start, rooms] = GenerateGraph(Parse(*aocpp::Startup(argc, argv)));
auto const graph = GenerateGraph(input); std::cout << "Part 1: " << Part1(start, rooms) << std::endl;
auto const routes = Routes(graph, 30); std::cout << "Part 2: " << Part2(start, rooms) << std::endl;
std::uint64_t p1 = 0;
for (auto const& kv : routes) {
p1 = std::max(p1, kv.second);
}
std::cout << "Part 1: " << p1 << std::endl;
//Part2(snapshots, std::cout << "Part 2:\n");
} }