Add solution for "Day 20: Race Condition", part 2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user