// Solutions to the Advent Of Code 2024. // Copyright (C) 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 RaceCondition::RaceCondition(const int threshold) : threshold_{ threshold } { } const std::string RaceCondition::getPuzzleName() const { return "Race Condition"; } const int RaceCondition::getPuzzleDay() const { return 20; } void RaceCondition::finish() { // Vector of positions that form the path. The index of an element is also the time at which the position is passed. std::vector path{}; path.reserve(static_cast(threshold_) * 2); path.push_back(findChar(getStartChar())); bool isMoving{ true }; while (isMoving) { // Checks if there is a cheat leading to the current position. checkCheat(path); // Progresses the race path. isMoving = tryFindNextPathPosition(path); } } constexpr char RaceCondition::getStartChar() { return 'S'; } constexpr char RaceCondition::getWallChar() { return '#'; } constexpr int RaceCondition::getPart1CheatLength() { return 2; } constexpr int RaceCondition::getPart2CheatLength() { return 20; } bool RaceCondition::tryFindNextPathPosition(std::vector& path) { auto previous = path.size() <= 1 ? Point2{ -1, -1 } : path[path.size() - 2]; for (const auto& direction : Point2::cardinalDirections) { auto next = path.back() + direction; if (next != previous && getCharAt(next) != getWallChar()) { path.push_back(next); return true; } } return false; } void RaceCondition::checkCheat(const std::vector& 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(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(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(std::max(thresholdMinusTimeSaved >> 1, cheatLengthDiff), 1); } } }