AoC code coverage
Current view: top level - puzzles/2023 - Day23.cpp (source / functions) Coverage Total Hit
Test: master Lines: 91.9 % 247 227
Test Date: 2025-12-11 19:43:23 Functions: 100.0 % 9 9

            Line data    Source code
       1              : #include "Grid2d.h"
       2              : #include "LinewiseInput.h"
       3              : #include "PuzzleImpl.h"
       4              : #include "Vector.h"
       5              : 
       6              : #include <absl/container/flat_hash_map.h>
       7              : #include <absl/container/flat_hash_set.h>
       8              : #include <libassert/assert.hpp>
       9              : 
      10              : #include <algorithm>
      11              : #include <cstdint>
      12              : #include <iterator>
      13              : #include <limits>
      14              : #include <queue>
      15              : #include <stack>
      16              : #include <string>
      17              : #include <string_view>
      18              : #include <utility>
      19              : #include <vector>
      20              : 
      21              : namespace {
      22              : using Coord = Grid2d<char>::Coord;
      23              : enum Dir : uint8_t { N = 0, E = 1, S = 2, W = 3 };
      24              : constexpr std::array<Dir, 4u> directions = {N, E, S, W};
      25              : constexpr std::array<Dir, 4u> op = {S, W, N, E};
      26              : constexpr std::array<Coord, 4u> stencil = {{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}};
      27              : 
      28              : class DAG {
      29              : public:
      30              :   using Vertex = Coord;
      31              : 
      32            1 :   DAG(Grid2dSpan<char const> const map) {
      33              : 
      34            1 :     std::queue<std::tuple<DAG::Vertex, Dir>> queue;
      35              : 
      36            1 :     absl::flat_hash_set<Vertex> visited;
      37              : 
      38            1 :     _start = Coord{0, map.ySize() - 1};
      39            2 :     while (map[_start] != '.' && _start.x() < map.xSize())
      40            1 :       ++_start.x();
      41            1 :     DEBUG_ASSERT(_start.x() < map.xSize());
      42            1 :     visited.insert(_start);
      43            1 :     queue.emplace(_start, S);
      44              : 
      45           86 :     while (!queue.empty()) {
      46           85 :       auto [vertex, dir] = queue.front();
      47           85 :       queue.pop();
      48              : 
      49           85 :       Coord coord = vertex;
      50           85 :       int edgeWeight = 0;
      51           85 :       bool vertexFound = false;
      52           85 :       Dir newDir = N;
      53         9514 :       while (!vertexFound) {
      54         9429 :         coord += stencil[dir];
      55         9429 :         ++edgeWeight;
      56         9429 :         DEBUG_ASSERT(map[coord] != '#', coord, dir);
      57              : 
      58         9429 :         if (coord.y() == 0) {
      59            1 :           DEBUG_ASSERT(map[coord] == '.');
      60            1 :           _end = coord;
      61            1 :           visited.insert(coord);
      62            1 :           _edges[vertex].push_back(coord);
      63            1 :           _edgeWeights.emplace(std::pair{vertex, coord}, edgeWeight);
      64            1 :           vertexFound = true;
      65            1 :           continue;
      66            1 :         }
      67              : 
      68         9428 :         int pathCtr = 0;
      69         9428 :         switch (map[coord]) {
      70            0 :         case '^':
      71            0 :           if (dir == S)
      72            0 :             vertexFound = true;
      73            0 :           dir = N;
      74            0 :           break;
      75           79 :         case '>':
      76           79 :           if (dir == W)
      77           21 :             vertexFound = true;
      78           79 :           dir = E;
      79           79 :           break;
      80           64 :         case 'v':
      81           64 :           if (dir == N)
      82            4 :             vertexFound = true;
      83           64 :           dir = S;
      84           64 :           break;
      85            0 :         case '<':
      86            0 :           if (dir == E)
      87            0 :             vertexFound = true;
      88            0 :           dir = W;
      89            0 :           break;
      90         9285 :         case '.':
      91         9285 :           pathCtr = 0;
      92        37140 :           for (auto d : directions) {
      93        37140 :             if (d == op[dir])
      94         9285 :               continue;
      95        27855 :             if (map[coord + stencil[d]] != '#') {
      96         9376 :               ++pathCtr;
      97         9376 :               newDir = d;
      98         9376 :             }
      99        27855 :           }
     100         9285 :           DEBUG_ASSERT(pathCtr > 0, coord);
     101         9285 :           if (pathCtr == 1) {
     102         9226 :             dir = newDir;
     103         9226 :           } else {
     104           59 :             vertexFound = true;
     105           59 :             DEBUG_ASSERT(std::ranges::find(_edges[vertex], coord) == _edges[vertex].end());
     106           59 :             _edges[vertex].push_back(coord);
     107           59 :             _edgeWeights.emplace(std::pair{vertex, coord}, edgeWeight);
     108           59 :             if (!visited.insert(coord).second)
     109           25 :               continue;
     110          136 :             for (auto d : directions) {
     111          136 :               if (d == op[dir])
     112           34 :                 continue;
     113          102 :               if (map[coord + stencil[d]] != '#') {
     114           84 :                 queue.emplace(coord, d);
     115           84 :               }
     116          102 :             }
     117           34 :           }
     118         9260 :           break;
     119         9260 :         case '#':
     120            0 :         default:
     121            0 :           UNREACHABLE(map[coord], coord);
     122         9428 :         }
     123         9428 :       }
     124           85 :     }
     125              : 
     126            1 :     absl::flat_hash_map<Vertex, absl::flat_hash_set<Vertex>> inEdges;
     127            1 :     for (auto const &[v, us] : _edges)
     128           35 :       for (auto const &u : us)
     129           60 :         inEdges[u].insert(v);
     130              : 
     131            1 :     absl::flat_hash_set<Vertex> vertices;
     132            1 :     vertices.insert(_start);
     133           37 :     while (!vertices.empty()) {
     134           36 :       Vertex v = *vertices.begin();
     135           36 :       vertices.erase(v);
     136           36 :       _vertices.push_back(v);
     137           60 :       for (auto &u : _edges[v]) {
     138           60 :         inEdges[u].erase(v);
     139           60 :         if (inEdges[u].empty())
     140           35 :           vertices.insert(u);
     141           60 :       }
     142           36 :     }
     143            1 :     ASSERT(std::ranges::all_of(inEdges,
     144            1 :                                [](auto const &us) { return us.second.empty(); })); // Check if DAG
     145            1 :   }
     146              : 
     147            1 :   [[nodiscard]] int longestPath() const {
     148            1 :     absl::flat_hash_map<Vertex, int> distances;
     149            1 :     absl::flat_hash_map<Vertex, Vertex> pred;
     150            1 :     for (auto const &v : _vertices)
     151           36 :       distances[v] = std::numeric_limits<int>::max();
     152            1 :     distances[_start] = 0;
     153           36 :     for (auto const &u : _vertices) {
     154           36 :       auto vIt = _edges.find(u);
     155           36 :       if (vIt == _edges.end())
     156            0 :         continue;
     157           60 :       for (auto const &v : vIt->second) {
     158           60 :         auto eIt = _edgeWeights.find(std::pair{u, v});
     159           60 :         DEBUG_ASSERT(eIt != _edgeWeights.end());
     160           60 :         if (distances[v] > distances[u] - eIt->second) {
     161           42 :           distances[v] = distances[u] - eIt->second;
     162           42 :           pred[v] = u;
     163           42 :         }
     164           60 :       }
     165           36 :     }
     166              : 
     167            1 :     return -distances[_end];
     168            1 :   }
     169              : 
     170              : private:
     171              :   std::vector<Vertex> _vertices;
     172              :   Vertex _start;
     173              :   Vertex _end;
     174              :   absl::flat_hash_map<Vertex, std::vector<Vertex>> _edges;
     175              :   absl::flat_hash_map<std::pair<Vertex, Vertex>, int> _edgeWeights;
     176              : };
     177              : 
     178              : class Graph {
     179              : public:
     180            1 :   Graph(Grid2dSpan<char const> const map) {
     181              : 
     182            1 :     std::queue<std::tuple<DAG::Vertex, Dir>> queue;
     183              : 
     184            1 :     absl::flat_hash_map<Coord, size_t> foundVertices;
     185              : 
     186           36 :     auto addVertex = [&](Coord const &c) {
     187           36 :       DEBUG_ASSERT(_outEdges.size() == _numVertices);
     188           36 :       DEBUG_ASSERT(_edgeWeights.size() == _numVertices);
     189           36 :       foundVertices[c] = _numVertices;
     190           36 :       _outEdges.emplace_back();
     191           36 :       _edgeWeights.emplace_back();
     192           36 :       return _numVertices++;
     193           36 :     };
     194              : 
     195           85 :     auto addEdge = [&](size_t const v1, size_t v2, int const edgeWeight) {
     196           85 :       DEBUG_ASSERT(v1 < _edgeWeights.size());
     197           85 :       DEBUG_ASSERT(v2 < _edgeWeights.size());
     198           85 :       DEBUG_ASSERT(_edgeWeights.size() == _outEdges.size());
     199              : 
     200           85 :       auto it = std::ranges::find(_outEdges[v1], v2);
     201           85 :       if (it == _outEdges[v1].end()) {
     202           60 :         DEBUG_ASSERT(!std::ranges::contains(_outEdges[v2], v1));
     203           60 :         _outEdges[v1].push_back(v2);
     204           60 :         _outEdges[v2].push_back(v1);
     205           60 :         _edgeWeights[v1].push_back(edgeWeight);
     206           60 :         _edgeWeights[v2].push_back(edgeWeight);
     207           60 :       } else {
     208           25 :         auto idx = std::distance(_outEdges[v1].begin(), it);
     209           25 :         if (_edgeWeights[v1][idx] >= edgeWeight)
     210           25 :           return;
     211            0 :         _edgeWeights[v1][idx] = edgeWeight;
     212            0 :         it = std::ranges::find(_outEdges[v2], v1);
     213            0 :         DEBUG_ASSERT(it != _outEdges[v2].end());
     214            0 :         idx = std::distance(_outEdges[v2].begin(), it);
     215            0 :         DEBUG_ASSERT(_edgeWeights[v2][idx] < edgeWeight);
     216            0 :         _edgeWeights[v2][idx] = edgeWeight;
     217            0 :       }
     218           85 :     };
     219              : 
     220            1 :     Coord start{0, map.ySize() - 1};
     221            2 :     while (map[start] != '.' && start.x() < map.xSize())
     222            1 :       ++start.x();
     223            1 :     DEBUG_ASSERT(start.x() < map.xSize());
     224            1 :     _start = addVertex(start);
     225            1 :     queue.emplace(start, S);
     226              : 
     227           86 :     while (!queue.empty()) {
     228           85 :       auto [vertexCoord, dir] = queue.front();
     229           85 :       size_t vertex = foundVertices[vertexCoord];
     230           85 :       queue.pop();
     231              : 
     232           85 :       Coord coord = vertexCoord;
     233           85 :       int edgeWeight = 0;
     234           85 :       bool vertexFound = false;
     235           85 :       Dir newDir = N;
     236        12708 :       while (!vertexFound) {
     237        12708 :         coord += stencil[dir];
     238        12708 :         ++edgeWeight;
     239        12708 :         DEBUG_ASSERT(map[coord] != '#', coord, dir);
     240              : 
     241        12708 :         if (coord.y() == 0) {
     242            1 :           DEBUG_ASSERT(map[coord] == '.');
     243            1 :           _end = addVertex(coord);
     244            1 :           addEdge(vertex, _end, edgeWeight);
     245            1 :           break;
     246            1 :         }
     247              : 
     248        12707 :         int pathCtr = 0;
     249        12707 :         if (map[coord] != '#') {
     250        12707 :           pathCtr = 0;
     251        50828 :           for (auto d : directions) {
     252        50828 :             if (d == op[dir])
     253        12707 :               continue;
     254        38121 :             if (map[coord + stencil[d]] != '#') {
     255        12843 :               ++pathCtr;
     256        12843 :               newDir = d;
     257        12843 :             }
     258        38121 :           }
     259        12707 :           DEBUG_ASSERT(pathCtr > 0, coord);
     260        12707 :           if (pathCtr == 1) {
     261        12623 :             dir = newDir;
     262        12623 :           } else {
     263           84 :             auto it = foundVertices.find(coord);
     264           84 :             bool newVertex = it == foundVertices.end();
     265           84 :             size_t foundVertex = newVertex ? addVertex(coord) : it->second;
     266           84 :             addEdge(vertex, foundVertex, edgeWeight);
     267           84 :             if (newVertex)
     268          136 :               for (auto d : directions) {
     269          136 :                 if (d == op[dir])
     270           34 :                   continue;
     271          102 :                 if (map[coord + stencil[d]] != '#') {
     272           84 :                   queue.emplace(coord, d);
     273           84 :                 }
     274          102 :               }
     275           84 :             break;
     276           84 :           }
     277        12707 :         }
     278        12707 :       }
     279           85 :     }
     280            1 :   }
     281              : 
     282            1 :   int longestPath() {
     283            1 :     ASSERT(_numVertices <= 64);
     284            1 :     int maxWeight = 0;
     285            1 :     longestPathImpl(_start, 0, 0, maxWeight);
     286            1 :     return maxWeight;
     287            1 :   }
     288              : 
     289              : private:
     290              :   void longestPathImpl(size_t const node, uint64_t const visited, int const weight,
     291    102288677 :                        int &maxWeight) {
     292    102288677 :     uint64_t const nodeMask = uint64_t(1) << node;
     293              : 
     294    102288677 :     if ((visited & nodeMask) != 0u)
     295     71708383 :       return;
     296              : 
     297     30580294 :     if (node == _end) {
     298      1262816 :       maxWeight = std::max(weight, maxWeight);
     299      1262816 :       return;
     300      1262816 :     }
     301              : 
     302    131606154 :     for (size_t i = 0; i < _outEdges[node].size(); ++i)
     303    102288676 :       longestPathImpl(_outEdges[node][i], visited | nodeMask, weight + _edgeWeights[node][i],
     304    102288676 :                       maxWeight);
     305     29317478 :   }
     306              : 
     307              :   size_t _numVertices = 0u;
     308              :   std::vector<std::vector<size_t>> _outEdges;
     309              :   std::vector<std::vector<int>> _edgeWeights;
     310              :   size_t _start;
     311              :   size_t _end;
     312              : };
     313              : 
     314              : } // namespace
     315              : 
     316            1 : template <> std::string solvePart1<2023, 23>(std::string_view const input) {
     317            1 :   LinewiseInput lines(input);
     318            1 :   Grid2d<char> map = lines.makeCharGrid2dWithGhostLayers(1, '#');
     319            1 :   DAG dag(map);
     320            1 :   return std::to_string(dag.longestPath());
     321            1 : }
     322              : 
     323            1 : template <> std::string solvePart2<2023, 23>(std::string_view const input) {
     324            1 :   LinewiseInput lines(input);
     325            1 :   Grid2d<char> map = lines.makeCharGrid2dWithGhostLayers(1, '#');
     326              : 
     327            1 :   Graph g(map);
     328              : 
     329            1 :   return std::to_string(g.longestPath());
     330            1 : }
        

Generated by: LCOV version 2.0-1