Add solution for "Day 23: LAN Party", part 2
This commit is contained in:
parent
f6b9e7b034
commit
958adde4a2
12
README.md
12
README.md
@ -174,6 +174,18 @@ The solver calculates all secret numbers using only fast bit operations to find
|
||||
|
||||
After each secret number has been calculated, the new price and price change are determined for part 2. For the first occurrence of each sequence of four consecutive changes per monkey buyer, the solver tracks the last price of that sequence. Adding them up per monkey buyer as they are discovered, the maximum of these sums is the target value.
|
||||
|
||||
### Day 23: LAN Party
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2024/day/23>, :white_check_mark: Solver: [`LanParty.cpp`](src/LanParty.cpp)
|
||||
|
||||
This solver was the driver of unifying and generalizing the [graph implementation](include/aoc/common/Graph.hpp) used already for [day 16](#day-16-reindeer-maze) and [day 18](#day-18-ram-run).
|
||||
|
||||
For part one, the algorithm takes each vertex starting with `t` and checks for each pair of its neighbors if they are connected. If so, a unique 3-clique has been found.
|
||||
|
||||
The general problem of finding a maximum clique in a graph is *NP hard*, but the constraints and the graph structure allow for a fast algorithm. Firstly, it is notable that each vertex has the same degree, which suggests that the clique number (the size of the maximum clique) might be close to that, and again at least one vertex starting with `t` shall be included.
|
||||
|
||||
Thus the algorithm starts with the maximum degree as target clique number, which must be an upper bound if the graph is connected and itself not complete. It will loop over all `t`-vertices and try to find a clique of that size, which includes the vertex, by checking all combinations of the vertex' neighbors of that size. If unsuccessful, the target number is decreased and another iteration started. However, it turns out that the clique number is indeed equal to the maximum degree, so only one loop is required.
|
||||
|
||||
## Thanks
|
||||
|
||||
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.
|
||||
|
@ -36,4 +36,8 @@ class LanParty
|
||||
int findOrAddVertex(const std::string& vertexId);
|
||||
void computeInterconnectedThreeSetCount(const int vertexTx);
|
||||
bool canProcessVertex(const int vertexToCheck, const int vertexTx) const;
|
||||
bool findCompleteSubgraph(const int vertex, const size_t targetSize);
|
||||
bool isSubgraphComplete(const std::vector<int>& neighbors, const std::vector<bool>& combination) const;
|
||||
std::string calcPassword(const std::vector<int>& neighbors, const std::vector<bool>& combination,
|
||||
const int vertex) const;
|
||||
};
|
||||
|
@ -15,6 +15,9 @@
|
||||
|
||||
#include <aoc/LanParty.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
const std::string LanParty::getPuzzleName() const
|
||||
{
|
||||
return "LAN Party";
|
||||
@ -32,12 +35,29 @@ void LanParty::processDataLine(const std::string& line)
|
||||
|
||||
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);
|
||||
itTx++;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,20 +94,20 @@ void LanParty::computeInterconnectedThreeSetCount(const int vertexTx)
|
||||
if (canProcessVertex(itFirstNeighbor->vertex, vertexTx))
|
||||
{
|
||||
auto itSecondNeighbor = itFirstNeighbor;
|
||||
itSecondNeighbor++;
|
||||
++itSecondNeighbor;
|
||||
while (itSecondNeighbor != lan_.end())
|
||||
{
|
||||
if (canProcessVertex(itSecondNeighbor->vertex, vertexTx))
|
||||
{
|
||||
if (lan_.areAdjacent(itFirstNeighbor->vertex, itSecondNeighbor->vertex))
|
||||
{
|
||||
part1++;
|
||||
++part1;
|
||||
}
|
||||
}
|
||||
itSecondNeighbor++;
|
||||
++itSecondNeighbor;
|
||||
}
|
||||
}
|
||||
itFirstNeighbor++;
|
||||
++itFirstNeighbor;
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,3 +116,72 @@ bool LanParty::canProcessVertex(const int vertexToCheck, const int vertexTx) con
|
||||
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; });
|
||||
}
|
||||
|
@ -406,10 +406,10 @@ TEST_CASE("[LanPartyTests]")
|
||||
TestContext test;
|
||||
SECTION("FullData")
|
||||
{
|
||||
test.runFull(std::make_unique<LanParty>(), 1230, "");
|
||||
test.runFull(std::make_unique<LanParty>(), 1230, "az,cj,kp,lm,lt,nj,rf,rx,sn,ty,ui,wp,zo");
|
||||
}
|
||||
SECTION("ExampleData")
|
||||
{
|
||||
test.runExample(std::make_unique<LanParty>(), 7, "");
|
||||
test.runExample(std::make_unique<LanParty>(), 7, "co,de,ka,ta");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user