Add solution for "Day 20: Race Condition", part 2

This commit is contained in:
2025-06-04 23:29:27 +02:00
parent 43f1798343
commit e1eb8fbe2b
6 changed files with 80 additions and 37 deletions

View File

@@ -32,33 +32,19 @@ const int RaceCondition::getPuzzleDay() const
void RaceCondition::finish()
{
int time{ 0 };
Grid<int> times{ lines.size(), lines[0].size() };
// Fills the grid with a number that is guaranteed to be greater than the length of the path.
times.fill(static_cast<int>(times.getNColumns() * times.getNRows()));
// Vector of positions that form the path. The index of an element is also the time at which the position is passed.
std::vector<Point2> path{};
path.reserve(static_cast<size_t>(threshold_) * 2);
Point2 position{ findChar(getStartChar()) };
Point2 previous{ -1, -1 };
while (position != previous)
path.push_back(findChar(getStartChar()));
bool isMoving{ true };
while (isMoving)
{
// Tracks time for current position.
times.cell(position) = time++;
// Checks if there is a cheat leading to the current position.
checkCheat(position, times);
checkCheat(path);
// Progresses the race path.
auto oldPosition = position;
for (const auto& direction : Point2::cardinalDirections)
{
auto next = position + direction;
if (next != previous && getCharAt(next) != getWallChar())
{
position = next;
break;
}
}
previous = oldPosition;
isMoving = tryFindNextPathPosition(path);
}
}
@@ -72,20 +58,60 @@ constexpr char RaceCondition::getWallChar()
return '#';
}
constexpr int RaceCondition::getCheatLength()
constexpr int RaceCondition::getPart1CheatLength()
{
return 2;
}
void RaceCondition::checkCheat(const Point2& position, Grid<int>& times)
constexpr int RaceCondition::getPart2CheatLength()
{
auto time = times.cell(position);
for (auto& direction : doubleSteps_)
return 20;
}
bool RaceCondition::tryFindNextPathPosition(std::vector<Point2>& path)
{
auto previous = path.size() <= 1 ? Point2{ -1, -1 } : path[path.size() - 2];
for (const auto& direction : Point2::cardinalDirections)
{
auto other = position + direction;
if (isInBounds(other) && time >= threshold_ + times.cell(other) + getCheatLength())
auto next = path.back() + direction;
if (next != previous && getCharAt(next) != getWallChar())
{
part1++;
path.push_back(next);
return true;
}
}
return false;
}
void RaceCondition::checkCheat(const std::vector<Point2>& path)
{
// Checks previously encountered path positions that are at least the threshold away from the current position in
// reverse order for valid cheat opportunities.
int64_t i{ static_cast<int64_t>(path.size()) - threshold_ - 2 };
while (i >= 0)
{
int64_t distance{ path.back().calcManhattanDistance(path[i]) };
// Checks if the time saved by the cheat reaches at least the threshold, and if the cheat is not longer than
// permitted. The permitted cheat time is longer for part 2 than for part 1.
int64_t thresholdMinusTimeSaved{ threshold_ - static_cast<int64_t>(path.size()) + i + distance };
int64_t cheatLengthDiff{ distance - getPart2CheatLength() };
if (thresholdMinusTimeSaved < 0 && cheatLengthDiff <= 0)
{
part2++;
if (distance <= getPart1CheatLength())
{
part1++;
}
i--;
}
else
{
// Backtracks the path as much as possible for the next potential position. This is possible because we know
// that the 'distance' between 'path.back()' and 'path[i]' cannot change by more than the change of 'i',
// since the positions in 'path' are contiguous. In other words, from this iteration to the next, both
// 'cheatLengthDiff' and 'thresholdMinusTimeSaved / 2' cannot change more than 'i'. We use this to skip the
// positions that cannot fulfill the condition above.
i -= std::max<int64_t>(std::max(thresholdMinusTimeSaved >> 1, cheatLengthDiff), 1);
}
}
}

View File

@@ -15,6 +15,8 @@
#include <aoc/common/Point2.hpp>
#include <math.h>
const Point2 Point2::left{ -1, 0 };
const Point2 Point2::right{ 1, 0 };
const Point2 Point2::up{ 0, -1 };
@@ -44,7 +46,6 @@ Point2 Point2::getCardinalDirection(const char directionChar)
}
}
Point2::Point2()
: Point2{ 0, 0 }
{
@@ -55,6 +56,11 @@ Point2::Point2(const int x, const int y)
{
}
int Point2::calcManhattanDistance(const Point2& other) const
{
return std::abs(x - other.x) + std::abs(y - other.y);
}
bool Point2::operator==(const Point2& rhs) const
{
return x == rhs.x && y == rhs.y;