solver: prune unplaceable large squares
A skyline may retain enough total empty area while no longer containing a box for its largest remaining square. Scan for the required consecutive low columns and reject such monotonic dead states. Keep each pruning combination independently measurable and record the small public-path gain, the direct-order-9 regression, and the rejected periodic schedule. Tests: Debug CTest (13 passed) Tests: ASan+UBSan CTest (13 passed) Refs: #15
This commit was merged in pull request #27.
This commit is contained in:
@@ -152,6 +152,8 @@ namespace {
|
||||
size_t prune_hits = 0;
|
||||
size_t valley_capacity_checks = 0;
|
||||
size_t valley_capacity_prunes = 0;
|
||||
size_t large_square_checks = 0;
|
||||
size_t large_square_prunes = 0;
|
||||
size_t generated_tasks = 0;
|
||||
size_t completed_tasks = 0;
|
||||
};
|
||||
@@ -172,8 +174,10 @@ namespace {
|
||||
|
||||
/** Cheap necessary conditions applied before branching at a search node. */
|
||||
enum class Pruning {
|
||||
disabled,
|
||||
valley_capacity,
|
||||
disabled = 0,
|
||||
valley_capacity = 1,
|
||||
large_square = 2,
|
||||
all = 3,
|
||||
};
|
||||
|
||||
/** Return whether a cell is the canonical representative of its D4 orbit.
|
||||
@@ -259,7 +263,46 @@ namespace {
|
||||
return available_area >= required_area;
|
||||
}
|
||||
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity>
|
||||
/** Return whether the skyline contains an empty box for a square.
|
||||
*
|
||||
* A side-k square fits exactly when k consecutive columns have heights no
|
||||
* greater than board height minus k. Tracking the current qualifying run
|
||||
* checks this in one pass without allocations.
|
||||
*/
|
||||
[[nodiscard]] auto skyline_has_empty_square(
|
||||
std::vector<size_t> const &skyline, size_t const side) noexcept -> bool {
|
||||
assert(side > 0);
|
||||
assert(side <= skyline.size());
|
||||
auto const maximum_height = skyline.size() - side;
|
||||
size_t run = 0;
|
||||
for (auto const height: skyline) {
|
||||
run = height <= maximum_height ? run + 1 : 0;
|
||||
if (run == side) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return whether the largest remaining square has any feasible position.
|
||||
*
|
||||
* Feasible empty boxes are monotonic in the side length: a box which fits
|
||||
* the largest remaining square also fits every smaller one. It is therefore
|
||||
* sufficient to test only the largest size with non-zero multiplicity.
|
||||
*/
|
||||
[[nodiscard]] auto remaining_large_square_fits(
|
||||
std::vector<size_t> const &skyline, Avail const &available) noexcept
|
||||
-> bool {
|
||||
for (auto side = available.size() - 1; side != 0; --side) {
|
||||
if (available[side] != 0) {
|
||||
return skyline_has_empty_square(skyline, side);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity,
|
||||
bool PruneLargeSquare>
|
||||
auto search_skyline(size_t const n, size_t const length,
|
||||
SearchPolicy const policy,
|
||||
std::vector<size_t> &skyline, Avail &available,
|
||||
@@ -288,6 +331,19 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if constexpr (PruneLargeSquare) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_checks;
|
||||
++counters->large_square_checks;
|
||||
}
|
||||
if (!remaining_large_square_fits(skyline, available)) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_hits;
|
||||
++counters->large_square_prunes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto const largest =
|
||||
std::min({n, valley.width, length - valley.height});
|
||||
auto try_side = [&](size_t const side) {
|
||||
@@ -320,7 +376,8 @@ namespace {
|
||||
side, valley.height + side);
|
||||
squares.emplace_back(valley.x + valley.height * length, side);
|
||||
|
||||
if (search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity>(
|
||||
if (search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
||||
PruneLargeSquare>(
|
||||
n, length, policy, skyline, available, squares, counters)) {
|
||||
return true;
|
||||
}
|
||||
@@ -367,7 +424,8 @@ namespace {
|
||||
* candidates and updating up to n columns for each costs O(n^2), for
|
||||
* O(board width + n^2) local work per node and the same total state.
|
||||
*/
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity>
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity,
|
||||
bool PruneLargeSquare>
|
||||
auto search_solution_impl(size_t const n, SearchPolicy const policy,
|
||||
SearchCounters *const counters) noexcept
|
||||
-> Results {
|
||||
@@ -380,29 +438,50 @@ namespace {
|
||||
std::vector<Square> squares;
|
||||
squares.reserve(length);
|
||||
static_cast<void>(
|
||||
search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity>(
|
||||
n, length, policy, skyline, available, squares, counters));
|
||||
search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
||||
PruneLargeSquare>(
|
||||
n, length, policy, skyline, available, squares, counters));
|
||||
|
||||
return {length, std::move(squares)};
|
||||
}
|
||||
|
||||
template<bool Instrument, bool BreakD4Symmetry>
|
||||
auto search_solution_dispatch(size_t const n, SearchPolicy const policy,
|
||||
Pruning const pruning,
|
||||
SearchCounters *const counters) noexcept
|
||||
-> Results {
|
||||
switch (pruning) {
|
||||
case Pruning::all:
|
||||
return search_solution_impl<Instrument, BreakD4Symmetry, true, true>(
|
||||
n, policy, counters);
|
||||
case Pruning::valley_capacity:
|
||||
return search_solution_impl<Instrument, BreakD4Symmetry, true, false>(
|
||||
n, policy, counters);
|
||||
case Pruning::large_square:
|
||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, true>(
|
||||
n, policy, counters);
|
||||
case Pruning::disabled:
|
||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, false>(
|
||||
n, policy, counters);
|
||||
}
|
||||
assert(false);
|
||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, false>(
|
||||
n, policy, counters);
|
||||
}
|
||||
|
||||
auto search_solution(
|
||||
size_t const n,
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning = Pruning::valley_capacity) noexcept
|
||||
Pruning const pruning = Pruning::all) noexcept
|
||||
-> Results {
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<false, true, true>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, true, false>(n, policy, nullptr);
|
||||
return search_solution_dispatch<false, true>(
|
||||
n, policy, pruning, nullptr);
|
||||
}
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<false, false, true>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, false, false>(n, policy, nullptr);
|
||||
return search_solution_dispatch<false, false>(
|
||||
n, policy, pruning, nullptr);
|
||||
}
|
||||
|
||||
auto search_solution_instrumented(size_t const n,
|
||||
@@ -412,21 +491,15 @@ namespace {
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning =
|
||||
Pruning::valley_capacity) noexcept
|
||||
Pruning::all) noexcept
|
||||
-> Results {
|
||||
counters = {};
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<true, true, true>(
|
||||
n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, true, false>(n, policy, &counters);
|
||||
return search_solution_dispatch<true, true>(
|
||||
n, policy, pruning, &counters);
|
||||
}
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<true, false, true>(
|
||||
n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, false, false>(n, policy, &counters);
|
||||
return search_solution_dispatch<true, false>(
|
||||
n, policy, pruning, &counters);
|
||||
}
|
||||
|
||||
/** Construct an odd-order solution from its even-order predecessor. */
|
||||
@@ -464,7 +537,7 @@ namespace {
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning = Pruning::valley_capacity) noexcept -> Results {
|
||||
Pruning const pruning = Pruning::all) noexcept -> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(
|
||||
n, search_solution(n - 1, policy, symmetry, pruning));
|
||||
@@ -479,7 +552,7 @@ namespace {
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning =
|
||||
Pruning::valley_capacity) noexcept
|
||||
Pruning::all) noexcept
|
||||
-> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(
|
||||
|
||||
Reference in New Issue
Block a user