Grid::next_pos was starting its scan from the position after the current one. This is inefficient as we know how large the square we've just placed is as we scan those positions. This optimisation just skips that square when starting the scan.
205 lines
5.6 KiB
C++
205 lines
5.6 KiB
C++
//
|
|
// Created by Matthew Gretton-Dann on 31/08/2025.
|
|
//
|
|
|
|
#include <cassert>
|
|
#include <utility>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <iostream>
|
|
|
|
using Pos = std::pair<int, int>;
|
|
|
|
auto x(Pos const &p) noexcept -> int { return p.first; }
|
|
auto y(Pos const &p) noexcept -> int { return p.second; }
|
|
|
|
struct Square {
|
|
Square(Pos const &pos, int length) noexcept : pos_(pos), length_(length) {
|
|
}
|
|
|
|
Square(Square const &other) noexcept = default;
|
|
|
|
Square(Square &&other) noexcept = default;
|
|
|
|
Square &operator=(Square const &other) noexcept = default;
|
|
|
|
Square &operator=(Square &&other) noexcept = default;
|
|
|
|
~Square() noexcept = default;
|
|
|
|
auto x() const noexcept -> int { return ::x(pos_); }
|
|
auto y() const noexcept -> int { return ::y(pos_); }
|
|
auto length() const noexcept -> int { return length_; }
|
|
|
|
Pos pos_;
|
|
int length_;
|
|
};
|
|
|
|
struct OverlappingSquares {
|
|
Pos pos_;
|
|
};
|
|
|
|
struct Grid {
|
|
Grid(int length) : grid_(length * length, '.'), length_(length) {
|
|
}
|
|
|
|
Grid(Grid const &other) = default;
|
|
|
|
Grid(Grid &&other) noexcept = default;
|
|
|
|
Grid &operator=(Grid const &other) = default;
|
|
|
|
Grid &operator=(Grid &&other) noexcept = default;
|
|
|
|
~Grid() noexcept = default;
|
|
|
|
auto length() const noexcept -> int { return length_; }
|
|
|
|
auto set(int x, int y, char c) noexcept -> void {
|
|
assert(x < length_);
|
|
assert(y < length_);
|
|
assert(grid_[x + y * length_] != c);
|
|
grid_[x + y * length_] = c;
|
|
}
|
|
|
|
auto get(int x, int y) const noexcept -> char {
|
|
assert(x < length_);
|
|
assert(y < length_);
|
|
return grid_[x + y * length_];
|
|
}
|
|
|
|
auto add(Square const &sq) -> void {
|
|
switch (sq.length()) {
|
|
case 1: set(sq.x(), sq.y(), '*');
|
|
break;
|
|
case 2: set(sq.x(), sq.y(), '+');
|
|
set(sq.x() + 1, sq.y(), '+');
|
|
set(sq.x(), sq.y() + 1, '+');
|
|
set(sq.x() + 1, sq.y() + 1, '+');
|
|
break;
|
|
default: {
|
|
auto n = sq.length();
|
|
set(sq.x(), sq.y(), '+');
|
|
set(sq.x() + n - 1, sq.y(), '+');
|
|
set(sq.x(), sq.y() + n - 1, '+');
|
|
set(sq.x() + n - 1, sq.y() + n - 1, '+');
|
|
for (int i = 1; i < n - 1; ++i) {
|
|
set(sq.x() + i, sq.y(), '-');
|
|
set(sq.x() + i, sq.y() + n - 1, '-');
|
|
set(sq.x(), sq.y() + i, '|');
|
|
for (int j = 1; j < n - 1; ++j) {
|
|
set(sq.x() + j, sq.y() + i, ' ');
|
|
}
|
|
set(sq.x() + n - 1, sq.y() + i, '|');
|
|
}
|
|
|
|
int i = sq.x() + n - 1;
|
|
while (n != 0) {
|
|
set(--i, sq.y() + 1, '0' + (n % 10));
|
|
n /= 10;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto clear(Square const &sq) {
|
|
for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) {
|
|
for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) {
|
|
set(i, j, '.');
|
|
}
|
|
}
|
|
}
|
|
|
|
auto output() const {
|
|
for (auto i = 0; i < length_; ++i) {
|
|
std::cout << grid_.substr(i * length_, length_) << '\n';
|
|
}
|
|
}
|
|
|
|
/** \brief Get length of the largest square that fits at \a pos in the grid.
|
|
*/
|
|
auto largest_square(Pos const &pos, int n) const noexcept -> int {
|
|
assert(x(pos) < length_);
|
|
assert(y(pos) < length_);
|
|
|
|
/* Because of how we walk through the grid (starting at 0,0 then increasing
|
|
* x followed by y) we can assume that if the position (b, y) is clear
|
|
* (i.e. a '.') then (b, y + i) is clear for all i > 0.
|
|
*
|
|
* This means we only need to look for the first non-clear position along the
|
|
* current row.
|
|
*/
|
|
auto b = x(pos);
|
|
// Make sure we don't go looking in the next row.
|
|
auto e = std::min(x(pos) + n, length_);
|
|
while (b < e) {
|
|
if (grid_[b + y(pos) * length_] != '.') { break; }
|
|
++b;
|
|
}
|
|
return b - x(pos);
|
|
}
|
|
|
|
/** Get the next position to check. n is the size of the square we just
|
|
* added.
|
|
*/
|
|
auto next_pos(Pos const &pos, int n) const noexcept -> Pos {
|
|
const auto p = x(pos) + n + y(pos) * length_;
|
|
const auto next_p = grid_.find('.', p);
|
|
if (next_p == std::string::npos) {
|
|
return std::make_pair(0, length_);
|
|
}
|
|
return std::make_pair(next_p % length_, next_p / length_);
|
|
}
|
|
|
|
std::string grid_;
|
|
int length_;
|
|
};
|
|
|
|
auto triangle_num(int n) { return (n * (n + 1)) / 2; }
|
|
|
|
using Avail = std::vector<int>;
|
|
|
|
auto find_solution_impl(Grid &grid, int n, Pos const &pos, Avail &avail_sqs) -> bool {
|
|
if (x(pos) == 0 && y(pos) == grid.length()) { return true; }
|
|
|
|
/* Walk through the possible squares from the largest that will fit down to 0.
|
|
* We don't want to walk up from 1 to the largest because we can show that a
|
|
* 1x1 piece will never fit along an edge (or one in from the edge) - as
|
|
* there is no other 1x1 piece to go alongside it). This means we know the
|
|
* first guess is definitely wrong if we start small.
|
|
*
|
|
* If we know a square of side length N will fit then we know a square of side length
|
|
* N - 1 will fit.
|
|
*/
|
|
for (auto idx = grid.largest_square(pos, n); idx != 0; --idx) {
|
|
if (avail_sqs[idx] == 0) { continue; }
|
|
|
|
auto const sq = Square(pos, idx);
|
|
--avail_sqs[idx];
|
|
grid.add(sq);
|
|
if (find_solution_impl(grid, n, grid.next_pos(pos, idx), avail_sqs)) { return true; }
|
|
++avail_sqs[idx];
|
|
grid.clear(sq);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
auto find_solution(int n) -> Grid {
|
|
auto length = triangle_num(n);
|
|
Grid grid(length);
|
|
Avail avail_sqs;
|
|
for (auto i = 0; i <= n; ++i) { avail_sqs.push_back(i); }
|
|
find_solution_impl(grid, n, std::make_pair(0, 0), avail_sqs);
|
|
return grid;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
auto n = (argc == 1) ? 8 : atoi(argv[1]);
|
|
auto grid = find_solution(n);
|
|
std::cout << "Partridge problem " << n << " side length " << grid.length() << '\n';
|
|
grid.output();
|
|
return 0;
|
|
}
|