2023-01-29 16:47:44 -08:00
|
|
|
|
/// @file 16.cpp
|
|
|
|
|
/// @brief Solution to Advent of Code 2022 Day 16
|
|
|
|
|
///
|
|
|
|
|
/// This solution follows the following process:
|
|
|
|
|
/// 1. Parse the input source into a list of rooms
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// 2. Optimization: put rooms with valves first in the array
|
|
|
|
|
/// 3. Compute the shortest paths between each room
|
|
|
|
|
/// 4. Enumerate all the paths through the graph to find maximum water flow per valve-set.
|
|
|
|
|
/// 5. Use the flow/valve summaries to compute the two answers.
|
2023-01-29 16:47:44 -08:00
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
#include <bitset>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <sstream>
|
2023-04-07 09:45:55 -07:00
|
|
|
|
#include <stack>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <tuple>
|
2023-01-28 14:46:00 -08:00
|
|
|
|
#include <unordered_map>
|
2023-01-29 12:16:47 -08:00
|
|
|
|
#include <vector>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
#include <boost/multi_array.hpp>
|
2023-01-28 14:46:00 -08:00
|
|
|
|
#include <boost/phoenix.hpp>
|
2023-01-29 12:16:47 -08:00
|
|
|
|
#include <boost/range/adaptors.hpp>
|
2023-01-28 14:46:00 -08:00
|
|
|
|
#include <boost/range/algorithm.hpp>
|
2023-01-29 12:16:47 -08:00
|
|
|
|
#include <boost/range/irange.hpp>
|
|
|
|
|
#include <boost/spirit/include/qi.hpp>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
|
|
|
|
#include <doctest.h>
|
|
|
|
|
|
2023-04-08 12:08:51 -07:00
|
|
|
|
#include <aocpp/Parsing.hpp>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
#include <aocpp/Startup.hpp>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2023-01-30 21:07:23 -08:00
|
|
|
|
namespace phx = boost::phoenix;
|
|
|
|
|
namespace qi = boost::spirit::qi;
|
|
|
|
|
|
2023-02-01 11:23:04 -08:00
|
|
|
|
/// @brief The starting room as defined in the problem statement
|
|
|
|
|
constexpr char const* STARTING_ROOM = "AA";
|
|
|
|
|
|
|
|
|
|
/// @brief Arbitrary number of valves this solution supports
|
|
|
|
|
constexpr std::size_t MAX_VALVES = 64;
|
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Array of distances from one node to another.
|
|
|
|
|
/// @tparam T distance type
|
|
|
|
|
///
|
|
|
|
|
/// `distance[i][j]` is the cost to move from node `i` to node `j`
|
|
|
|
|
template <typename T>
|
|
|
|
|
using distance_array = boost::multi_array<T, 2>;
|
2023-01-29 12:16:47 -08:00
|
|
|
|
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @brief Update single-step distance matrix with transitive shortest paths
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @tparam T distance type
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @param[in,out] dist distance matrix
|
|
|
|
|
///
|
|
|
|
|
/// This implementation uses the Floyd–Warshall algorithm and assumes that
|
|
|
|
|
/// there are no negative-cost cycles. It also assumes that a path exists
|
|
|
|
|
/// between all pairs of nodes.
|
2023-01-29 16:47:44 -08:00
|
|
|
|
template <typename T>
|
|
|
|
|
auto ShortestDistances(distance_array<T> & dist) -> void
|
2023-01-28 08:39:08 -08:00
|
|
|
|
{
|
|
|
|
|
auto const range = boost::irange(dist.size());
|
|
|
|
|
for (auto const k : range) {
|
|
|
|
|
for (auto const i : range) {
|
|
|
|
|
for (auto const j : range) {
|
2023-02-01 11:23:04 -08:00
|
|
|
|
auto const d_ikj = dist[i][k] + dist[k][j];
|
|
|
|
|
auto & d_ij = dist[i][j];
|
2023-02-02 07:57:37 -08:00
|
|
|
|
if (d_ij > d_ikj) d_ij = d_ikj;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief A single record from the problem input.
|
2023-01-29 12:16:47 -08:00
|
|
|
|
struct Room {
|
|
|
|
|
/// @brief Name of the room
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::string name;
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @brief Flow rate of the valve in the room
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::uint64_t flow;
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @brief Directly adjacent rooms
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::vector<std::string> connections;
|
|
|
|
|
};
|
|
|
|
|
|
2023-04-08 12:08:51 -07:00
|
|
|
|
class Grammar : public qi::grammar<std::string::const_iterator, std::vector<Room>()> {
|
|
|
|
|
qi::rule<iterator_type, std::string()> name;
|
|
|
|
|
qi::rule<iterator_type, Room()> room_description;
|
|
|
|
|
qi::rule<iterator_type, std::vector<Room>()> room_descriptions;
|
2023-01-28 14:46:00 -08:00
|
|
|
|
|
2023-04-08 12:08:51 -07:00
|
|
|
|
public:
|
|
|
|
|
Grammar() : grammar::base_type{room_descriptions} {
|
|
|
|
|
using namespace qi::labels;
|
|
|
|
|
name = qi::as_string[+qi::alpha];
|
|
|
|
|
room_description =
|
2023-01-28 08:39:08 -08:00
|
|
|
|
"Valve " >>
|
2023-01-29 12:16:47 -08:00
|
|
|
|
name [ phx::bind(&Room::name, _val) = _1 ] >>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
" has flow rate=" >>
|
2023-01-29 12:16:47 -08:00
|
|
|
|
qi::ulong_long [ phx::bind(&Room::flow, _val) = _1 ] >>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
"; tunnel" >> -qi::string("s") >>
|
|
|
|
|
" lead" >> -qi::string("s") >>
|
|
|
|
|
" to valve" >> -qi::string("s") >>
|
|
|
|
|
" " >>
|
2023-04-08 12:08:51 -07:00
|
|
|
|
(name % ", ") [ phx::bind(&Room::connections, _val) = _1 ] >>
|
|
|
|
|
"\n";
|
|
|
|
|
room_descriptions = *room_description;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
2023-04-08 12:08:51 -07:00
|
|
|
|
};
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @brief Rearrange the rooms so that those with flows come first
|
|
|
|
|
/// @param[in,out] rooms vector of rooms that gets reordered
|
|
|
|
|
/// @return number of rooms with with non-zero flows
|
|
|
|
|
auto FlowsFirst(
|
|
|
|
|
std::vector<Room> & rooms
|
|
|
|
|
) -> std::size_t
|
|
|
|
|
{
|
2023-01-30 21:07:23 -08:00
|
|
|
|
using namespace phx::placeholders;
|
|
|
|
|
auto const zeros = boost::range::partition(rooms, phx::bind(&Room::flow, arg1) > 0);
|
|
|
|
|
return std::distance(boost::begin(rooms), zeros);
|
2023-01-30 12:25:39 -08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @brief Computes the distances between rooms and finds the address of the starting room
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @param[in] rooms input list of rooms
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @returns starting index and distances
|
|
|
|
|
auto GenerateDistances(
|
|
|
|
|
std::vector<Room> const& rooms
|
2023-01-29 16:47:44 -08:00
|
|
|
|
) -> std::pair<std::size_t, distance_array<std::uint64_t>>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
{
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto const N = rooms.size();
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
// Associate the names and indexes of each room
|
|
|
|
|
std::unordered_map<std::string, std::size_t> names;
|
2023-04-07 09:45:55 -07:00
|
|
|
|
for (auto const [i,room] : boost::adaptors::index(rooms)) {
|
2023-01-29 12:16:47 -08:00
|
|
|
|
names[room.name] = i;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto distances = distance_array<std::uint64_t>{boost::extents[N][N]};
|
2023-01-29 12:16:47 -08:00
|
|
|
|
|
2023-01-30 21:07:23 -08:00
|
|
|
|
for (auto const i : boost::irange(rooms.size())) {
|
2023-01-29 12:16:47 -08:00
|
|
|
|
auto di = distances[i];
|
2023-02-01 11:23:04 -08:00
|
|
|
|
|
|
|
|
|
// Default value: N is longer than any optimal distance by at least 1
|
|
|
|
|
boost::range::fill(di, N);
|
2023-01-29 12:16:47 -08:00
|
|
|
|
|
|
|
|
|
// each room is one away from adjacent rooms
|
2023-02-01 11:23:04 -08:00
|
|
|
|
for (auto const& name : rooms[i].connections) {
|
2023-01-29 12:16:47 -08:00
|
|
|
|
di[names[name]] = 1;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
2023-01-29 12:16:47 -08:00
|
|
|
|
|
|
|
|
|
// zero distance to self
|
|
|
|
|
di[i] = 0;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShortestDistances(distances);
|
|
|
|
|
|
2023-02-01 11:23:04 -08:00
|
|
|
|
return {names.at(STARTING_ROOM), std::move(distances)};
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Bitset used to track which valves have been turned on
|
2023-02-01 11:23:04 -08:00
|
|
|
|
using Valves = std::bitset<MAX_VALVES>;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Intermediate states for depth-first search in Routes
|
2023-01-28 08:39:08 -08:00
|
|
|
|
struct State {
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Time remaining
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::uint64_t time;
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Water flow achieved so far
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::uint64_t flow;
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Current actor location
|
2023-01-28 08:39:08 -08:00
|
|
|
|
std::size_t location;
|
2023-01-29 16:47:44 -08:00
|
|
|
|
/// @brief Set of valves already opened
|
2023-01-28 08:39:08 -08:00
|
|
|
|
Valves valves;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @brief Compute all the flows achievable with a set of values
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @param[in] start Index of starting room
|
|
|
|
|
/// @param[in] initial_time Initial amount of time
|
|
|
|
|
/// @param[in] rooms Array of rooms from input file
|
|
|
|
|
/// @param[in] distances Shortest paths between all pairs of rooms
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @return Mapping of maximum flow achievable using a particular set of valves
|
2023-01-28 14:46:00 -08:00
|
|
|
|
auto Routes(
|
|
|
|
|
std::size_t const start,
|
2023-01-29 12:16:47 -08:00
|
|
|
|
std::uint64_t const initial_time,
|
|
|
|
|
std::vector<Room> const& rooms,
|
2023-01-29 16:47:44 -08:00
|
|
|
|
distance_array<std::uint64_t> const& distances
|
2023-01-28 14:46:00 -08:00
|
|
|
|
) -> std::unordered_map<Valves, std::uint64_t>
|
2023-01-28 08:39:08 -08:00
|
|
|
|
{
|
2023-02-01 11:23:04 -08:00
|
|
|
|
if (rooms.size() > MAX_VALVES) {
|
|
|
|
|
throw std::runtime_error{"Too many valves"};
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
// Maximal flow seen at each set of open valves
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto result = std::unordered_map<Valves, std::uint64_t>{};
|
2023-01-29 12:16:47 -08:00
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
// Remaining states for depth first search
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto states = std::stack<State>{};
|
|
|
|
|
states.push({initial_time, 0, start, {}});
|
|
|
|
|
|
2023-01-28 08:39:08 -08:00
|
|
|
|
while (!states.empty()) {
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto const state = states.top();
|
|
|
|
|
states.pop();
|
2023-01-28 08:39:08 -08:00
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
if (auto & best = result[state.valves]; best < state.flow) {
|
|
|
|
|
best = state.flow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto const distances_i = distances[state.location];
|
|
|
|
|
|
2023-04-07 09:45:55 -07:00
|
|
|
|
for (auto const [j, room] : boost::adaptors::index(rooms)) {
|
2023-01-28 14:46:00 -08:00
|
|
|
|
// don't revisit a valve
|
2023-01-29 12:16:47 -08:00
|
|
|
|
if (state.valves.test(j)) { continue; }
|
2023-01-28 14:46:00 -08:00
|
|
|
|
|
|
|
|
|
// don't visit rooms we can't get to in time
|
2023-01-29 16:47:44 -08:00
|
|
|
|
// +1 accounts for the cost of actually turning the valve
|
|
|
|
|
auto const cost = distances_i[j] + 1;
|
|
|
|
|
if (cost >= state.time) { continue; }
|
2023-01-28 14:46:00 -08:00
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
auto const time = state.time - cost;
|
2023-01-30 21:07:23 -08:00
|
|
|
|
auto const flow = state.flow + room.flow * time;
|
2023-01-28 14:46:00 -08:00
|
|
|
|
auto valves = state.valves;
|
2023-01-29 12:16:47 -08:00
|
|
|
|
valves.set(j);
|
2023-01-28 14:46:00 -08:00
|
|
|
|
|
2023-04-07 09:45:55 -07:00
|
|
|
|
states.push({time, flow, static_cast<std::size_t>(j), valves});
|
2023-01-28 14:46:00 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @brief Maximize the water flow using a single actor and 30 minutes
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @param[in] start Index of the starting room
|
|
|
|
|
/// @param[in] rooms Rooms from input file
|
|
|
|
|
/// @param[in] distances Shortest distances between pairs of rooms
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @return Maximum flow achievable
|
|
|
|
|
auto Part1(
|
|
|
|
|
std::size_t const start,
|
|
|
|
|
std::vector<Room> const& rooms,
|
2023-01-29 16:47:44 -08:00
|
|
|
|
distance_array<std::uint64_t> const& distances
|
2023-01-29 12:16:47 -08:00
|
|
|
|
) -> std::uint64_t
|
2023-01-28 14:46:00 -08:00
|
|
|
|
{
|
2023-01-30 21:07:23 -08:00
|
|
|
|
auto const routes = Routes(start, 30, rooms, distances);
|
2023-01-28 14:46:00 -08:00
|
|
|
|
return *boost::range::max_element(routes | boost::adaptors::map_values);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 21:07:23 -08:00
|
|
|
|
/// @brief Maximize the water flow using two actors and 26 minutes
|
2023-01-30 12:25:39 -08:00
|
|
|
|
/// @param[in] start Index of the starting room
|
|
|
|
|
/// @param[in] rooms Rooms from input file
|
|
|
|
|
/// @param[in] distances Shortest distances between pairs of rooms
|
2023-01-29 12:16:47 -08:00
|
|
|
|
/// @return Maximum flow achievable
|
|
|
|
|
auto Part2(
|
|
|
|
|
std::size_t const start,
|
|
|
|
|
std::vector<Room> const& rooms,
|
2023-01-29 16:47:44 -08:00
|
|
|
|
distance_array<std::uint64_t> const& distances
|
2023-01-29 12:16:47 -08:00
|
|
|
|
) -> std::uint64_t
|
2023-01-28 14:46:00 -08:00
|
|
|
|
{
|
2023-01-30 21:07:23 -08:00
|
|
|
|
auto const routes = Routes(start, 26, rooms, distances);
|
2023-01-28 14:46:00 -08:00
|
|
|
|
auto const end = routes.end();
|
2023-02-01 11:23:04 -08:00
|
|
|
|
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto best = std::uint64_t{0};
|
2023-01-30 21:07:23 -08:00
|
|
|
|
for (auto it1 = routes.begin(); it1 != end; ++it1) {
|
|
|
|
|
for (auto it2 = std::next(it1); it2 != end; ++it2) {
|
2023-01-28 14:46:00 -08:00
|
|
|
|
// only consider pairs that have disjoint sets of valves
|
|
|
|
|
if ((it1->first & it2->first).none()) {
|
2023-01-29 16:47:44 -08:00
|
|
|
|
best = std::max(best, it1->second + it2->second);
|
2023-01-28 14:46:00 -08:00
|
|
|
|
}
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-29 16:47:44 -08:00
|
|
|
|
return best;
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2023-02-01 11:23:04 -08:00
|
|
|
|
/// @brief Print solutions to parts 1 and 2
|
2023-01-31 21:29:04 -08:00
|
|
|
|
/// @param[in,out] in input text
|
|
|
|
|
/// @param[in,out] out output text
|
|
|
|
|
auto Main(std::istream & in, std::ostream & out) -> void
|
|
|
|
|
{
|
2023-04-08 12:08:51 -07:00
|
|
|
|
auto rooms = aocpp::ParseGrammar(Grammar{}, in);
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto const n = FlowsFirst(rooms); // reorders rooms
|
2023-01-31 21:29:04 -08:00
|
|
|
|
auto const [start, distances] = GenerateDistances(rooms);
|
|
|
|
|
rooms.resize(n); // forget about the rooms with no flow
|
|
|
|
|
out << "Part 1: " << Part1(start, rooms, distances) << std::endl;
|
|
|
|
|
out << "Part 2: " << Part2(start, rooms, distances) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 16:47:44 -08:00
|
|
|
|
TEST_SUITE("2022-16") {
|
2023-01-28 08:39:08 -08:00
|
|
|
|
TEST_CASE("example") {
|
|
|
|
|
std::istringstream in {
|
2023-01-28 14:46:00 -08:00
|
|
|
|
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
|
|
|
|
|
)"};
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto out = std::ostringstream{};
|
2023-01-31 21:29:04 -08:00
|
|
|
|
Main(in, out);
|
|
|
|
|
CHECK(out.str() == "Part 1: 1651\nPart 2: 1707\n");
|
2023-01-28 08:39:08 -08:00
|
|
|
|
}
|
2023-01-29 16:47:44 -08:00
|
|
|
|
|
|
|
|
|
TEST_CASE("shortest path") {
|
2023-04-07 09:45:55 -07:00
|
|
|
|
auto distances = distance_array<int>{boost::extents[4][4]};
|
2023-01-29 16:47:44 -08:00
|
|
|
|
std::fill_n(distances.data(), distances.num_elements(), 100);
|
|
|
|
|
|
|
|
|
|
distances[0][2] = -2;
|
|
|
|
|
distances[0][0] = 0;
|
|
|
|
|
distances[1][0] = 4;
|
|
|
|
|
distances[1][1] = 0;
|
|
|
|
|
distances[1][2] = 3;
|
|
|
|
|
distances[2][2] = 0;
|
|
|
|
|
distances[2][3] = 2;
|
|
|
|
|
distances[3][1] = -1;
|
|
|
|
|
distances[3][3] = 0;
|
|
|
|
|
ShortestDistances(distances);
|
|
|
|
|
|
|
|
|
|
CHECK(distances[0][0] == 0);
|
|
|
|
|
CHECK(distances[0][1] == -1);
|
|
|
|
|
CHECK(distances[0][2] == -2);
|
|
|
|
|
CHECK(distances[0][3] == 0);
|
|
|
|
|
|
|
|
|
|
CHECK(distances[1][0] == 4);
|
|
|
|
|
CHECK(distances[1][1] == 0);
|
|
|
|
|
CHECK(distances[1][2] == 2);
|
|
|
|
|
CHECK(distances[1][3] == 4);
|
|
|
|
|
|
|
|
|
|
CHECK(distances[2][0] == 5);
|
|
|
|
|
CHECK(distances[2][1] == 1);
|
|
|
|
|
CHECK(distances[2][2] == 0);
|
|
|
|
|
CHECK(distances[2][3] == 2);
|
|
|
|
|
|
|
|
|
|
CHECK(distances[3][0] == 3);
|
|
|
|
|
CHECK(distances[3][1] == -1);
|
|
|
|
|
CHECK(distances[3][2] == 1);
|
|
|
|
|
CHECK(distances[3][3] == 0);
|
|
|
|
|
}
|
2023-02-01 11:23:04 -08:00
|
|
|
|
}
|