aocpp/2019/18.cpp

172 lines
4.3 KiB
C++
Raw Normal View History

2022-11-09 23:18:43 -08:00
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <vector>
#include <set>
#include <string>
#include <tuple>
#include <bitset>
#include <deque>
#include <cctype>
2022-11-10 11:28:03 -08:00
#include <queue>
2022-11-09 23:18:43 -08:00
#include <map>
#include <aocpp/Startup.hpp>
#include <aocpp/Coord.hpp>
2022-11-11 21:39:36 -08:00
#include <aocpp/Grid.hpp>
2022-11-09 23:18:43 -08:00
2022-11-10 19:38:43 -08:00
using namespace aocpp;
2022-11-09 23:18:43 -08:00
// lowercase key
// uppercase door
// start at @
// wall #
// boring .
namespace {
2022-11-10 19:38:43 -08:00
Coord(* const directions[4])(Coord) = {Up, Down, Left, Right};
2022-11-09 23:18:43 -08:00
using Features = std::map<char, Coord>;
using Doors = std::bitset<26>;
using Distances = std::map<char,std::map<char, std::pair<std::int64_t, Doors>>>;
2022-11-10 17:46:30 -08:00
auto SetKey(Doors& doors, char key) {
return doors.set(std::toupper(key) - 'A');
}
2022-11-11 21:39:36 -08:00
auto FindFeatures(Grid const& grid) -> Features {
2022-11-10 19:38:43 -08:00
Features features;
2022-11-11 21:39:36 -08:00
grid.each([&](Coord c, char v) {
if ('#' != v && '.' != v) {
features[v] = c;
2022-11-09 23:18:43 -08:00
}
2022-11-11 21:39:36 -08:00
});
2022-11-09 23:18:43 -08:00
return features;
}
auto FindDistancesFrom(
2022-11-11 21:39:36 -08:00
Grid const& grid,
2022-11-10 19:38:43 -08:00
char const start_letter,
Coord const start
2022-11-09 23:18:43 -08:00
) {
std::map<char, std::pair<std::int64_t, Doors>> result;
2022-11-10 17:46:30 -08:00
2022-11-10 19:38:43 -08:00
std::deque<std::tuple<std::int64_t, Coord, Doors>> todo {{0, start, {}}};
2022-11-09 23:18:43 -08:00
2022-11-10 17:46:30 -08:00
std::set<Coord> seen;
for (; !todo.empty(); todo.pop_front()) {
2022-11-09 23:18:43 -08:00
auto [steps, here, doors] = todo.front();
2022-11-10 17:46:30 -08:00
// Don't visit the same coordinate twice
if (!seen.insert(here).second) continue;
2022-11-11 21:39:36 -08:00
auto const c = grid[here];
2022-11-10 17:46:30 -08:00
if (c == '#') continue; // avoid walls
// success, we've found a key, record the path
if (c != start_letter && std::islower(c)) {
2022-11-11 08:27:18 -08:00
result[c] = {steps, doors};
2022-11-10 17:46:30 -08:00
continue; // don't walk beyond the key
}
// Note any keys we encounter on our journey
if (std::isupper(c)) {
SetKey(doors, c);
}
// Visit all neighbors
2022-11-10 19:38:43 -08:00
for (auto const fn : directions) {
2022-11-10 17:46:30 -08:00
todo.emplace_back(steps+1, fn(here), doors);
2022-11-09 23:18:43 -08:00
}
}
return result;
}
2022-11-11 21:39:36 -08:00
auto FindDistances(Grid const& grid, Features const& features) {
2022-11-09 23:18:43 -08:00
Distances distances;
2022-11-10 19:38:43 -08:00
for (auto const [start_letter, start_coord] : features) {
2022-11-10 11:11:02 -08:00
if (!std::isupper(start_letter)) {
2022-11-11 21:39:36 -08:00
distances[start_letter] = FindDistancesFrom(grid, start_letter, start_coord);
2022-11-09 23:18:43 -08:00
}
}
return distances;
}
2022-11-10 11:28:03 -08:00
auto SolveMaze(
Distances const& distances,
2022-11-10 17:46:30 -08:00
std::string const initial_locations
2022-11-10 11:28:03 -08:00
) -> std::int64_t
{
2022-11-10 11:11:02 -08:00
// Track current positions and current set of keys in easy to compare form
2022-11-10 17:46:30 -08:00
using Visited = std::pair<std::string, unsigned long long>;
2022-11-10 11:11:02 -08:00
std::set<Visited> seen;
2022-11-10 19:38:43 -08:00
// Priority queue returning lowest path cost states first.
using PqElt = std::tuple<std::int64_t, std::string, Doors>;
using PqCmp = decltype([](PqElt const& x, PqElt const& y) {
return std::get<0>(x) > std::get<0>(y); });
std::priority_queue<PqElt, std::vector<PqElt>, PqCmp> todo;
2022-11-10 11:28:03 -08:00
todo.emplace(0, initial_locations, Doors());
2022-11-09 23:18:43 -08:00
2022-11-10 17:46:30 -08:00
while(!todo.empty()) {
2022-11-10 11:28:03 -08:00
auto [steps, locations, keys] = todo.top();
todo.pop();
2022-11-09 23:18:43 -08:00
if (keys.all()) { return steps; }
2022-11-10 17:46:30 -08:00
std::sort(locations.begin(), locations.end());
if (seen.emplace(locations, keys.to_ullong()).second) {
for (auto& location : locations) {
2022-11-10 19:38:43 -08:00
auto const save = location;
2022-11-10 11:11:02 -08:00
2022-11-11 08:27:18 -08:00
for (auto const& [next, costneed] : distances.at(location)) {
2022-11-10 19:38:43 -08:00
auto const [cost, need] = costneed;
2022-11-10 17:46:30 -08:00
if ((need & ~keys).none()) { // no missing keys
location = next;
auto keys_ = keys; SetKey(keys_, next);
todo.emplace(steps + cost, locations, keys_);
location = save;
2022-11-09 23:18:43 -08:00
}
}
}
}
}
throw std::runtime_error{"no solution to part 1"};
}
2022-11-10 19:38:43 -08:00
// Part 2 instructs us to update the map splitting it into 4 quadrants
2022-11-11 21:39:36 -08:00
auto Part2(Grid & grid, Features & features) {
2022-11-10 19:38:43 -08:00
auto const start = features['@'];
for (auto const fn : directions) {
2022-11-11 21:39:36 -08:00
grid[fn(start)] = '#';
2022-11-10 19:38:43 -08:00
}
2022-11-10 11:11:02 -08:00
features.erase('@');
2022-11-10 19:38:43 -08:00
features['^'] = Up(Left (start));
features['&'] = Down(Left (start));
features['*'] = Up(Right(start));
2022-11-10 11:11:02 -08:00
features['$'] = Down(Right(start));
}
2022-11-09 23:18:43 -08:00
} // namespace
auto main(int argc, char** argv) -> int {
auto grid = Grid::Parse(*Startup(argc, argv));
2022-11-11 21:39:36 -08:00
auto features = FindFeatures(grid);
auto distances = FindDistances(grid, features);
2022-11-10 17:46:30 -08:00
std::cout << "Part 1: " << SolveMaze(distances, "@") << std::endl;
2022-11-10 19:38:43 -08:00
2022-11-11 21:39:36 -08:00
Part2(grid, features);
distances = FindDistances(grid, features);
2022-11-10 17:46:30 -08:00
std::cout << "Part 2: " << SolveMaze(distances, "^&*$") << std::endl;
2022-11-09 23:18:43 -08:00
}