#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace aocpp; namespace { Coord(* const directions[4])(Coord) = {Up, Down, Left, Right}; using Name = std::string; using Portals = std::map; using Distances = std::map>>; struct Map { std::vector 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 const& names, Distances & distances, std::string const start_name, Coord const start ) { std::vector> result; std::deque> todo {{0, start}}; std::set 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 names; for (auto const& [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; std::set seen; // Priority queue returning lowest path cost states first. using PqElt = std::tuple; using PqCmp = decltype([](PqElt const& x, PqElt const& y) { return std::get<0>(x) > std::get<0>(y); }); std::priority_queue, PqCmp> todo; todo.emplace(0, 0, "-AA"); while(!todo.empty()) { auto const [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; }