188 lines
5.6 KiB
C++
188 lines
5.6 KiB
C++
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include <aoc/LanParty.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
|
|
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<int> 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<bool> 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<int>& neighbors, const std::vector<bool>& 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<int>& neighbors, const std::vector<bool>& combination,
|
|
const int vertex) const
|
|
{
|
|
std::vector<std::string> 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; });
|
|
}
|