// Solutions to the Advent Of Code 2024. // Copyright (C) 2024-2025 Stefan Müller // // This program is free software: you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation, either version 3 of the License, or (at your option) any later // version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . #include #include #include const std::string LanParty::getPuzzleName() const { return "LAN Party"; } const int LanParty::getPuzzleDay() const { return 23; } void LanParty::processDataLine(const std::string& line) { lan_.addEdge(findOrAddVertex(line.substr(0, 2)), findOrAddVertex(line.substr(3, 2))); } void LanParty::finish() { size_t targetCliqueNumber{ 0 }; auto itTx = labelMap_.lower_bound(getFirstTxComputerName()); const auto itTxEnd = labelMap_.upper_bound(getLastTxComputerName()); while (itTx != itTxEnd) { computeInterconnectedThreeSetCount(itTx->second); targetCliqueNumber = std::max(targetCliqueNumber, lan_.getDegree(itTx->second)); ++itTx; } // Assumes that the graph is connected, but not complete. Otherwise we would start with targetCliqueNumber + 1. // Also assumes that the clique number of the graph is close to its maximum degree, hence starting with a large // target number. bool isFound{ false }; while (!isFound && targetCliqueNumber > 2) { auto itTx = labelMap_.lower_bound(getFirstTxComputerName()); while (!isFound && itTx != itTxEnd) { isFound = findCompleteSubgraph(itTx->second, targetCliqueNumber); ++itTx; } --targetCliqueNumber; } } constexpr std::string LanParty::getFirstTxComputerName() { return "ta"; } constexpr std::string LanParty::getLastTxComputerName() { return "tz"; } int LanParty::findOrAddVertex(const std::string& vertexId) { const auto found = labelMap_.find(vertexId); if (found != labelMap_.end()) { return found->second; } else { int vertex = lan_.addVertex(vertexId); labelMap_.insert({ vertexId, vertex }); return vertex; } } void LanParty::computeInterconnectedThreeSetCount(const int vertexTx) { auto itFirstNeighbor = lan_.begin(vertexTx); while (itFirstNeighbor != lan_.end()) { if (canProcessVertex(itFirstNeighbor->vertex, vertexTx)) { auto itSecondNeighbor = itFirstNeighbor; ++itSecondNeighbor; while (itSecondNeighbor != lan_.end()) { if (canProcessVertex(itSecondNeighbor->vertex, vertexTx)) { if (lan_.areAdjacent(itFirstNeighbor->vertex, itSecondNeighbor->vertex)) { ++part1; } } ++itSecondNeighbor; } } ++itFirstNeighbor; } } bool LanParty::canProcessVertex(const int vertexToCheck, const int vertexTx) const { const std::string& label{ lan_.getVertexData(vertexToCheck) }; return (label[0] != 't' || label > lan_.getVertexData(vertexTx)); } bool LanParty::findCompleteSubgraph(const int vertex, const size_t targetSize) { // Validates that the degree of the start vertex is actually high enough for the target clique size. if (targetSize > lan_.getDegree(vertex) + 1) { return false; } // Neighbors of start vertex. std::vector neighbors{}; neighbors.reserve(lan_.getDegree(vertex)); auto it = lan_.begin(vertex); while (it != lan_.end()) { neighbors.push_back(it->vertex); ++it; } // Combination of neighbors, i.e. 'neighbor[i]' is selected if and only if 'combination[i]' is true. std::vector combination(targetSize - 1, true); combination.resize(neighbors.size()); do { if (isSubgraphComplete(neighbors, combination)) { part2 = calcPassword(neighbors, combination, vertex); return true; } } while (std::prev_permutation(combination.begin(), combination.end())); return false; } bool LanParty::isSubgraphComplete(const std::vector& neighbors, const std::vector& combination) const { for (size_t i = 0; i < combination.size(); ++i) { if (combination[i]) { for (size_t j = i + 1; j < combination.size(); ++j) { if (combination[j] && !lan_.areAdjacent(neighbors[i], neighbors[j])) { return false; } } } } return true; } std::string LanParty::calcPassword(const std::vector& neighbors, const std::vector& combination, const int vertex) const { std::vector completeSubgraph{ lan_.getVertexData(vertex) }; for (size_t i = 0; i < combination.size(); ++i) { if (combination[i]) { completeSubgraph.push_back(lan_.getVertexData(neighbors[i])); } } std::sort(completeSubgraph.begin(), completeSubgraph.end()); return std::accumulate(++completeSubgraph.begin(), completeSubgraph.end(), *completeSubgraph.begin(), [](const std::string& acc, const std::string& x) { return acc + "," + x; }); }