// 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; });
}