20
This commit is contained in:
parent
85deec0dae
commit
17728edb44
175
2019/20.cpp
Normal file
175
2019/20.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
#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>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
|
||||
#include <aocpp/Startup.hpp>
|
||||
#include <aocpp/Coord.hpp>
|
||||
|
||||
using namespace aocpp;
|
||||
|
||||
namespace {
|
||||
|
||||
Coord(* const directions[4])(Coord) = {Up, Down, Left, Right};
|
||||
|
||||
using Name = std::string;
|
||||
using Portals = std::map<std::string, Coord>;
|
||||
using Distances = std::map<Name,std::vector<std::pair<Name, std::int64_t>>>;
|
||||
|
||||
struct Map {
|
||||
std::vector<std::string> rows;
|
||||
|
||||
auto operator[](Coord c) -> char& { return rows[c.y][c.x]; }
|
||||
auto operator[](Coord c) const -> char { return rows[c.y][c.x]; }
|
||||
|
||||
static auto Parse(std::istream & in) -> Map {
|
||||
Map result;
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
result.rows.emplace_back(std::move(line));
|
||||
}
|
||||
return {result};
|
||||
}
|
||||
};
|
||||
|
||||
auto FindPortals(Map const& map) -> Portals {
|
||||
Portals portals;
|
||||
|
||||
std::int64_t w = map.rows[0].size();
|
||||
std::int64_t h = map.rows.size();
|
||||
|
||||
for (std::int64_t x = 1; x < w-1; x++) {
|
||||
for (std::int64_t y = 1; y < h-1; y++) {
|
||||
Coord const c = {x,y};
|
||||
auto const v = map[c];
|
||||
if (std::isupper(v)) {
|
||||
char polarity = x==1 || x==w-2 || y==1 || y==h-2
|
||||
? '-' : '+';
|
||||
if (map[Up(c)] == '.') {
|
||||
portals[{polarity, v, map[Down(c)]}] = Up(c);
|
||||
} else if (map[Down(c)] == '.') {
|
||||
portals[{polarity, map[Up(c)], v}] = Down(c);
|
||||
} else if (map[Left(c)] == '.') {
|
||||
portals[{polarity, v, map[Right(c)]}] = Left(c);
|
||||
} else if (map[Right(c)] == '.') {
|
||||
portals[{polarity, map[Left(c)], v}] = Right(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return portals;
|
||||
}
|
||||
|
||||
auto FindDistancesFrom(
|
||||
Map const& map,
|
||||
std::map<Coord, Name> const& names,
|
||||
Distances & distances,
|
||||
std::string const start_name,
|
||||
Coord const start
|
||||
) {
|
||||
std::vector<std::pair<Name, std::int64_t>> result;
|
||||
|
||||
std::deque<std::pair<std::int64_t, Coord>> todo {{0, start}};
|
||||
|
||||
std::set<Coord> seen;
|
||||
|
||||
for (; !todo.empty(); todo.pop_front()) {
|
||||
auto const [steps, here] = todo.front();
|
||||
|
||||
// Don't visit the same coordinate twice
|
||||
if (!seen.insert(here).second) continue;
|
||||
|
||||
auto const c = map[here];
|
||||
if (c != '.') continue; // avoid walls
|
||||
|
||||
// success, we've found a key, record the path
|
||||
if (auto it = names.find(here); it != names.end() && it->second != start_name) {
|
||||
result.emplace_back(it->second, steps);
|
||||
continue; // don't walk beyond the portal
|
||||
}
|
||||
|
||||
// Visit all neighbors
|
||||
for (auto const fn : directions) {
|
||||
todo.emplace_back(steps+1, fn(here));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto OtherName(Name name) {
|
||||
name[0] = name[0] == '+' ? '-' : '+';
|
||||
return name;
|
||||
}
|
||||
|
||||
auto FindDistances(Map const& map, Portals const& portals) {
|
||||
Distances distances;
|
||||
|
||||
std::map<Coord, Name> names;
|
||||
for (auto [k,v] : portals) {
|
||||
names[v] = k;
|
||||
}
|
||||
|
||||
for (auto const [start_name, start_coord] : portals) {
|
||||
distances[start_name] = FindDistancesFrom(map, names, distances, start_name, start_coord);
|
||||
}
|
||||
|
||||
return distances;
|
||||
}
|
||||
|
||||
auto SolveMaze(Distances const& distances, bool const recursive) -> std::int64_t
|
||||
{
|
||||
// Track current positions and current set of keys in easy to compare form
|
||||
using Visited = std::pair<int, Name>;
|
||||
std::set<Visited> seen;
|
||||
|
||||
// Priority queue returning lowest path cost states first.
|
||||
using PqElt = std::tuple<std::int64_t, int, Name>;
|
||||
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;
|
||||
|
||||
todo.emplace(0, 0, "-AA");
|
||||
|
||||
while(!todo.empty()) {
|
||||
auto [steps, depth, name] = todo.top();
|
||||
todo.pop();
|
||||
if (name == "-ZZ") { return steps; }
|
||||
if (seen.emplace(depth, name).second) {
|
||||
if (auto const it = distances.find(name); it != distances.end()) {
|
||||
for (auto const [next, cost] : it->second) {
|
||||
if (next == "-ZZ") {
|
||||
if (depth == 0) todo.emplace(steps + cost, depth, "-ZZ");
|
||||
} else {
|
||||
auto const depth_ = depth + (recursive ? (next[0]=='+' ? 1 : -1) : 0);
|
||||
if (depth_ >= 0) todo.emplace(steps + cost + 1, depth_, OtherName(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw std::runtime_error{"no solution"};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
auto map = Map::Parse(Startup(argc, argv));
|
||||
auto portals = FindPortals(map);
|
||||
auto distances = FindDistances(map, portals);
|
||||
|
||||
std::cout << "Part 1: " << SolveMaze(distances, false) << std::endl;
|
||||
std::cout << "Part 2: " << SolveMaze(distances, true) << std::endl;
|
||||
}
|
@ -43,5 +43,8 @@ target_link_libraries(15 aocpp intcode)
|
||||
add_executable(18 18.cpp)
|
||||
target_link_libraries(18 aocpp)
|
||||
|
||||
add_executable(20 20.cpp)
|
||||
target_link_libraries(20 aocpp)
|
||||
|
||||
add_executable(23 23.cpp)
|
||||
target_link_libraries(23 aocpp intcode)
|
||||
|
Loading…
Reference in New Issue
Block a user