aocpp/2022/16.cpp

248 lines
7.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bitset>
#include <cstdint>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include <boost/multi_array.hpp>
#include <boost/phoenix.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>
#include <boost/spirit/include/qi.hpp>
#include <doctest.h>
#include <aocpp/Startup.hpp>
namespace {
using distance_array = boost::multi_array<std::uint64_t, 2>;
/// @brief Update distance matrix with transitive shortest paths
/// @param dist Single-step distances between nodes (must be square)
auto ShortestDistances(distance_array & dist) -> void
{
// FloydWarshall_algorithm
auto const range = boost::irange(dist.size());
for (auto const k : range) {
for (auto const i : range) {
for (auto const j : range) {
auto const new_dist = dist[i][k] + dist[k][j];
auto & old_dist = dist[i][j];
if (old_dist > new_dist) old_dist = new_dist;
}
}
}
}
struct Room {
/// @brief Name of the room
std::string name;
/// @brief Flow rate of the valve in the room
std::uint64_t flow;
/// @brief Directly adjacent rooms
std::vector<std::string> connections;
};
auto Parse(std::istream & in) -> std::vector<Room>
{
std::vector<Room> result;
std::string line;
while (std::getline(in, line)) {
namespace phx = boost::phoenix;
namespace qi = boost::spirit::qi;
using namespace qi::labels;
using It = std::string::const_iterator;
qi::rule<It, std::string()> const name = qi::as_string[+qi::alpha];
qi::rule<It, Room()> const room_description =
"Valve " >>
name [ phx::bind(&Room::name, _val) = _1 ] >>
" has flow rate=" >>
qi::ulong_long [ phx::bind(&Room::flow, _val) = _1 ] >>
"; tunnel" >> -qi::string("s") >>
" lead" >> -qi::string("s") >>
" to valve" >> -qi::string("s") >>
" " >>
(name % ", ") [ phx::bind(&Room::connections, _val) = _1 ];
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"};
}
}
return result;
}
/// @brief Computes the distances between rooms and finds the address of the starting room
/// @returns starting index and distances
auto GenerateDistances(
std::vector<Room> const& rooms
) -> std::pair<std::size_t, distance_array>
{
auto const N = rooms.size();
// Associate the names and indexes of each room
std::unordered_map<std::string, std::size_t> names;
for (auto [i,room] : rooms | boost::adaptors::indexed()) {
names[room.name] = i;
}
distance_array distances{boost::extents[N][N]};
// N is longer than any optimal distance by at least 1
std::fill_n(distances.data(), distances.num_elements(), N);
for (auto [i, room] : rooms | boost::adaptors::indexed()) {
auto di = distances[i];
// each room is one away from adjacent rooms
for (auto const& name : room.connections) {
di[names[name]] = 1;
}
// zero distance to self
di[i] = 0;
}
ShortestDistances(distances);
return {names.at("AA"), std::move(distances)};
}
using Valves = std::bitset<64>;
struct State {
std::uint64_t time;
std::uint64_t flow;
std::size_t location;
Valves valves;
};
/// @brief Compute all the flows achievable with a set of values
/// @param start Index of starting room
/// @param initial_time Initial amount of time
/// @param rooms Array of rooms from input file
/// @param distances Shortest paths between all pairs of rooms
/// @return Mapping of maximum flow achievable using a particular set of valves
auto Routes(
std::size_t const start,
std::uint64_t const initial_time,
std::vector<Room> const& rooms,
distance_array const& distances
) -> std::unordered_map<Valves, std::uint64_t>
{
std::vector<State> states { State{initial_time, 0, start, {}} };
std::unordered_map<Valves, std::uint64_t> result;
while (!states.empty()) {
auto const state = states.back();
states.pop_back();
auto const i = state.location;
for (auto [j, room] : rooms | boost::adaptors::indexed()) {
// don't revisit a valve
if (state.valves.test(j)) { continue; }
// don't visit rooms with useless valves
if (room.flow == 0) { continue; }
// don't visit rooms we can't get to in time
auto const cost = distances[i][j];
if (cost+1 >= state.time) { continue; }
auto const time = state.time - cost - 1;
auto const flow = state.flow + room.flow * time;
auto valves = state.valves;
valves.set(j);
// remember the best we've seen for this valve-set
if (result[valves] < flow) {
result[valves] = flow;
}
states.push_back({time, flow, static_cast<std::size_t>(j), valves});
}
}
return result;
}
/// @brief Maximize the water flow using a single actor and 30 minutes
/// @param start Index of the starting room
/// @param rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable
auto Part1(
std::size_t const start,
std::vector<Room> const& rooms,
distance_array const& distances
) -> std::uint64_t
{
auto const routes = Routes(start, 30, rooms, distances);
return *boost::range::max_element(routes | boost::adaptors::map_values);
}
/// @brief Maximize the water flow using two actos and 26 minutes
/// @param start Index of the starting room
/// @param rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable
auto Part2(
std::size_t const start,
std::vector<Room> const& rooms,
distance_array const& distances
) -> std::uint64_t
{
auto const routes = Routes(start, 26, rooms, distances);
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;
}
} // namespace
TEST_SUITE("2022-16 examples") {
TEST_CASE("example") {
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 rooms = Parse(in);
auto const [start, distances] = GenerateDistances(rooms);
CHECK(1651 == Part1(start, rooms, distances));
CHECK(1707 == Part2(start, rooms, distances));
}
}
auto main(int argc, char** argv) -> int
{
auto const rooms = Parse(*aocpp::Startup(argc, argv));
auto const [start, distances] = GenerateDistances(rooms);
std::cout << "Part 1: " << Part1(start, rooms, distances) << std::endl;
std::cout << "Part 2: " << Part2(start, rooms, distances) << std::endl;
}