30using PolyCell = std::pair<int, int>;
32using CellSet = std::vector<PolyCell>;
35inline constexpr std::array<PolyCell, 4> polyNeighbors{
36 PolyCell{1, 0}, PolyCell{-1, 0}, PolyCell{0, 1}, PolyCell{0, -1}};
39inline CellSet normalizeCells(CellSet cells) {
43 int minX = cells.front().first;
44 int minY = cells.front().second;
45 for (
const auto& [
x,
y] : cells) {
46 minX = std::min(minX,
x);
47 minY = std::min(minY,
y);
49 for (
auto& [
x,
y] : cells) {
53 std::sort(cells.begin(), cells.end());
64inline std::vector<CellSet> fixedPolyominoes(
int n) {
68 std::set<CellSet> current{normalizeCells({{0, 0}})};
69 for (
int k = 1; k < n; ++k) {
70 std::set<CellSet> next;
71 for (
const auto& poly : current) {
72 const std::set<PolyCell> occupied(poly.begin(), poly.end());
73 for (
const auto& cell : poly) {
74 for (
const auto& [dx, dy] : polyNeighbors) {
75 const PolyCell candidate{cell.first + dx, cell.second + dy};
76 if (occupied.count(candidate)) {
80 grown.push_back(candidate);
81 next.insert(normalizeCells(std::move(grown)));
87 return {current.begin(), current.end()};
92inline CellSet transformCells(
const CellSet& cells,
int symmetry) {
94 out.reserve(cells.size());
95 for (
auto [
x,
y] : cells) {
99 for (
int rot = symmetry >> 1; rot > 0; --rot) {
105 out.push_back({
x,
y});
107 return normalizeCells(std::move(out));
118inline bool polyominoHasHole(
const CellSet& cells) {
122 int maxX = 0, maxY = 0;
123 for (
const auto& [
x,
y] : cells) {
124 maxX = std::max(maxX,
x);
125 maxY = std::max(maxY,
y);
127 const std::set<PolyCell> occupied(cells.begin(), cells.end());
130 const auto inside = [&](
int x,
int y) {
133 std::set<PolyCell> reached{{-1, -1}};
134 std::vector<PolyCell> stack{{-1, -1}};
135 while (!stack.empty()) {
136 const auto [
x,
y] = stack.back();
138 for (
const auto& [dx, dy] : polyNeighbors) {
139 const PolyCell next{
x + dx,
y + dy};
140 if (!inside(next.first, next.second) || occupied.count(next) ||
141 reached.count(next)) {
144 reached.insert(next);
145 stack.push_back(next);
149 for (
int x = 0;
x <= maxX; ++
x) {
150 for (
int y = 0;
y <= maxY; ++
y) {
151 const PolyCell cell{
x,
y};
152 if (!occupied.count(cell) && !reached.count(cell)) {
162inline CellSet canonicalFree(
const CellSet& cells) {
163 CellSet best = transformCells(cells, 0);
164 for (
int symmetry = 1; symmetry < 8; ++symmetry) {
165 CellSet candidate = transformCells(cells, symmetry);
166 if (candidate < best) {
167 best = std::move(candidate);
189inline std::vector<std::vector<PolyCell>> polyominoLoops(
const CellSet& cells) {
190 const std::set<PolyCell> occupied(cells.begin(), cells.end());
193 std::map<PolyCell, std::vector<PolyCell>> outgoing;
194 for (
const auto& [
x,
y] : cells) {
195 if (!occupied.count({x, y - 1})) {
196 outgoing[{
x,
y}].push_back({
x + 1,
y});
198 if (!occupied.count({x + 1, y})) {
199 outgoing[{
x + 1,
y}].push_back({
x + 1,
y + 1});
201 if (!occupied.count({x, y + 1})) {
202 outgoing[{
x + 1,
y + 1}].push_back({
x,
y + 1});
204 if (!occupied.count({x - 1, y})) {
205 outgoing[{
x,
y + 1}].push_back({
x,
y});
209 std::set<std::pair<PolyCell, PolyCell>> used;
210 std::vector<std::vector<PolyCell>> loops;
211 for (
const auto& [source, targets] : outgoing) {
212 for (
const PolyCell& first : targets) {
213 if (used.count({source, first})) {
216 used.insert({source, first});
217 std::vector<PolyCell> loop{source};
218 int dx = first.first - source.first;
219 int dy = first.second - source.second;
220 PolyCell current = first;
226 while (current != source) {
227 loop.push_back(current);
229 const std::array<PolyCell, 4> preferred{
230 PolyCell{dy, -dx}, PolyCell{dx, dy}, PolyCell{-dy, dx},
232 const std::vector<PolyCell>& edges = outgoing.at(current);
233 for (
const auto& [pdx, pdy] : preferred) {
234 const PolyCell target{current.first + pdx, current.second + pdy};
235 if (std::find(edges.begin(), edges.end(), target) == edges.end()) {
238 used.insert({current, target});
245 loops.push_back(std::move(loop));
253inline int64_t loopTwiceArea(
const std::vector<PolyCell>& loop) {
255 const std::size_t m = loop.size();
256 for (std::size_t i = 0; i < m; ++i) {
257 const PolyCell here = loop[i];
258 const PolyCell next = loop[(i + 1) % m];
259 twice +=
static_cast<int64_t
>(here.first) * next.second -
260 static_cast<int64_t
>(next.first) * here.second;
267std::vector<Point<T>> loopCorners(
const std::vector<PolyCell>& loop) {
268 std::vector<Point<T>> corners;
269 const std::size_t m = loop.size();
270 for (std::size_t i = 0; i < m; ++i) {
271 const PolyCell prev = loop[(i + m - 1) % m];
272 const PolyCell here = loop[i];
273 const PolyCell next = loop[(i + 1) % m];
274 const int64_t cross =
275 static_cast<int64_t
>(here.first - prev.first) * (next.second - here.second) -
276 static_cast<int64_t
>(here.second - prev.second) * (next.first - here.first);
278 corners.emplace_back(
static_cast<T
>(here.first),
static_cast<T
>(here.second));
292std::vector<Point<T>> polyominoOutline(
const CellSet& cells) {
293 for (
const auto& loop : polyominoLoops(cells)) {
294 if (loopTwiceArea(loop) > 0) {
295 return loopCorners<T>(loop);
313 std::vector<Polygon<Point<T>>> holes;
314 for (
const auto& loop : polyominoLoops(cells)) {
315 if (loopTwiceArea(loop) > 0) {
318 holes.emplace_back(loopCorners<T>(loop));
343template <
class T =
int>
345 std::vector<Polygon<Point<T>>> result;
350 std::set<detail::CellSet> freeForms;
351 for (
const auto& fixed : detail::fixedPolyominoes(
static_cast<int>(size))) {
352 freeForms.insert(detail::canonicalFree(fixed));
355 for (
const auto& form : freeForms) {
356 if (detail::polyominoHasHole(form)) {
359 result.emplace_back(detail::polyominoOutline<T>(form));
375template <
class T =
int>
376std::vector<Polygon<Point<T>>>
polyominoes(std::size_t n1, std::size_t n2) {
377 std::vector<Polygon<Point<T>>> result;
378 for (std::size_t size = n1; size <= n2; ++size) {
380 result.insert(result.end(), std::make_move_iterator(sized.begin()),
381 std::make_move_iterator(sized.end()));
396template <
class T =
int>
419template <
class T =
int>
421 std::vector<PolygonWithHoles<Point<T>>> result;
426 std::set<detail::CellSet> freeForms;
427 for (
const auto& fixed : detail::fixedPolyominoes(
static_cast<int>(size))) {
428 freeForms.insert(detail::canonicalFree(fixed));
431 for (
const auto& form : freeForms) {
432 result.push_back(detail::polyominoRegion<T>(form));
448template <
class T =
int>
450 std::vector<PolygonWithHoles<Point<T>>> result;
451 for (std::size_t size = n1; size <= n2; ++size) {
453 result.insert(result.end(), std::make_move_iterator(sized.begin()),
454 std::make_move_iterator(sized.end()));
469template <
class T =
int>
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
std::vector< Polygon< Point< T > > > polyominoes(std::size_t size)
Enumerates the free polyominoes of a given size as polygons.
Definition polyominoes.hpp:344
PolygonWithHoles() -> PolygonWithHoles< Point<>, NoLabel >
Definition polygonwithholes.hpp:3093
std::vector< PolygonWithHoles< Point< T > > > polyominoRegionsUpTo(std::size_t n)
Enumerates the free polyominoes of every size from 1 to n as regions.
Definition polyominoes.hpp:470
std::vector< Polygon< Point< T > > > polyominoesUpTo(std::size_t n)
Enumerates the free polyominoes of every size from 1 to n.
Definition polyominoes.hpp:397
std::vector< PolygonWithHoles< Point< T > > > polyominoRegions(std::size_t size)
Enumerates the free polyominoes of a given size as regions.
Definition polyominoes.hpp:420
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
Angular sorting of points around a center.