Numero di tetti distinti di un n X n quadrato con n-poliomino liberi


17

La più recente "simpatica" sequenza OEIS, A328020 , è stata appena pubblicata pochi minuti fa.

Numero di tetti distinti di un n X n quadrato con n-poliomino liberi.

Questa sequenza conta piastrellature fino a simmetrie del quadrato. La sequenza ha sei termini, ma mi piacerebbe vedere se la gente qui può estenderla ulteriormente.

Esempio

Perché n=4ci sono 22 di tali griglie, come mostrato in questa immagine dall'OEIS. Credito: Jeff Bowermaster, illustrazione di A328020 (4).A328020 (4)

Sfida

Come questa sfida precedente , l'obiettivo di questa sfida è calcolare il maggior numero possibile di termini in questa sequenza, che inizia 1, 1, 2, 22, 515, 56734e in cui l'ennesimo termine è il numero di piastrellature della griglia n X n con n-poliamino.

Esegui il codice per tutto il tempo che desideri. Il vincitore di questa sfida sarà l'utente che pubblicherà il maggior numero di termini della sequenza, insieme al proprio codice per generarlo. Se due utenti pubblicano lo stesso numero di termini, vince chi pubblica il loro ultimo termine.


3
Quindi si tratta di simmetrie modulo del quadrato?
Peter Taylor,

@PeterTaylor, esatto. L'ho chiarito nella domanda.
Peter Kagey,

Ingenuamente direi che l'ennesima voce richiederebbe il calcolo delle operazioni number_of_fixed_n_polyominoes ^ ( n -1). Quindi per n = 7, ciò richiederebbe 760 ^ 6 ≈ 2 ^ 57,4 operazioni. Probabilmente puoi
ridurlo

@Sliepen, mi aspetto che puoi ridurlo di molto semplicemente facendo un passo indietro. In particolare, ci sono molti polinomi fissi che non possono essere posizionati in un angolo e una volta che un poliomino valido viene posizionato da qualche parte, limita enormemente ciò che può essere posizionato accanto ad esso.
Peter Kagey,

@PeterKagey, hai ragione. Immagino che sia di aiuto se, dato che hai già posizionato gli m-polinomini, scegli la posizione successiva per provare a posizionare un poliomino nella peggiore posizione possibile, che puoi tagliarlo molto.
G. Sliepen,

Risposte:


9

Un'estensione al codice di @Grimy ottiene N = 8

Questo sottolinea che @Grimy merita la generosità:

Potrei potare l'albero di ricerca estendendo il codice per verificare, dopo ogni poliomino finito, che lo spazio libero rimanente non sia suddiviso in componenti di dimensioni non divisibili per N.

Su una macchina in cui il codice originale ha richiesto 2m11s per N = 7, sono necessari 1m4s e N = 8 è stato calcolato in 33h46m. Il risultato è 23437350133.

Ecco la mia aggiunta come diff:

--- tilepoly.c  2019-10-11 12:37:49.676351878 +0200
+++ tilepolyprune.c     2019-10-13 04:28:30.518736188 +0200
@@ -51,6 +51,30 @@
     return 1;
 } 

+static int check_component_sizes(u64 occupied, u64 total){
+    u64 queue[N*N];
+    while (total<N*N){
+        u64 count = 1;
+        u64 start = ctz(~occupied);
+        queue[0] = start;
+        occupied |= 1ul << start;
+        for(u64 current=0; current<count; ++current){
+            u64 free_adjacent = adjacency_matrix[queue[current]] & ~occupied;
+            occupied |= free_adjacent;
+            while (free_adjacent){
+                u64 next = ctz(free_adjacent);
+                free_adjacent &= ~(1ul << next);
+                queue[count++] = next;
+            }
+        }
+        if (count % N){
+            return 0;
+        }
+        total += count;
+    }
+    return 1;
+}
+
 static void recurse(u64 mino, u64 cell, u64 occupied, u64 adjacent, u64 forbidden)
 {
     if (cell >= N) {
@@ -61,6 +85,9 @@
             return;
         }

+        if(!check_component_sizes(occupied,N*mino))
+            return;
+
         u64 next = ctz(~occupied);
         board[next] = mino;
         recurse(mino, 1, occupied | 1ul << next, adjacency_matrix[next], 0);

Provalo online!


Questo è molto carino.
Anush,

Tutto ciò di cui abbiamo bisogno ora è una versione simd multithread :)
Anush

1
Oh, è davvero fantastico! In realtà ho considerato questa ottimizzazione, ma non pensavo che sarebbe bastato raggiungere N = 8 in un tempo ragionevole, quindi non mi sono preoccupato di implementarla.
Grimmy,

14

C, 7 termini

Il settimo termine è 19846102 . (I primi sei sono 1, 1, 2, 22, 515, 56734, come indicato nella domanda).

#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define N 7
#define ctz __builtin_ctzl

typedef uint64_t u64;

static u64 board[N*N] = { 0 };
static u64 adjacency_matrix[N*N] = { 0 };
static u64 count = 0;

static u64 check_symmetry()
{
    static const u64 symmetries[7][3] = {
        { 0,     +N, +1 },
        { N-1,   -1, +N },
        { N-1,   +N, -1 },
        { N*N-1, -1, -N },
        { N*N-1, -N, -1 },
        { N*N-N, +1, -N },
        { N*N-N, -N, +1 },
    };

    int order[N];

    for (u64 i = 0; i < 7; ++i) {
        u64 start = symmetries[i][0];
        u64 dcol = symmetries[i][1];
        u64 drow = symmetries[i][2];
        memset(order, 0xFF, N*sizeof(int));

        for (u64 row = 0, col = 0; col < N || (col = 0, ++row < N); ++col) {
            u64 base = board[col + N*row];
            u64 symmetry = board[start + dcol*col + drow*row];
            u64 lex = 0;

            while (order[lex] != symmetry && order[lex] != -1)
                ++lex;
            order[lex] = symmetry;

            if (lex < base)
                return 0;

            if (base < lex)
                break;
        }
    }

    return 1;
} 

static void recurse(u64 mino, u64 cell, u64 occupied, u64 adjacent, u64 forbidden)
{
    if (cell >= N) {
        ++mino;

        if (mino == N) {
            count += check_symmetry();
            return;
        }

        u64 next = ctz(~occupied);
        board[next] = mino;
        recurse(mino, 1, occupied | 1ul << next, adjacency_matrix[next], 0);
        return;
    }

    adjacent &= ~occupied & ~forbidden;
    while (adjacent) {
        u64 next = ctz(adjacent);
        adjacent &= ~(1ul << next);
        forbidden |= 1ul << next;
        board[next] = mino;
        recurse(mino, cell + 1, occupied | 1ul << next, adjacent | adjacency_matrix[next], forbidden);
    }
}

int main(void)
{
    for (u64 i = 0; i < N*N; ++i) {
        if (i % N)
            adjacency_matrix[i] |= 1ul << (i - 1);
        if (i / N)
            adjacency_matrix[i] |= 1ul << (i - N);
        if (i % N != N - 1)
            adjacency_matrix[i] |= 1ul << (i + 1);
        if (i / N != N - 1)
            adjacency_matrix[i] |= 1ul << (i + N);
    }

    recurse(0, 2, 3, 4 | 3 << N, 0);
    printf("%ld\n", count);
}

Provalo online! (per N = 6, poiché N = 7 sarebbe scaduto.)

Sulla mia macchina, N = 6 ha impiegato 0.171s e N = 7 ha impiegato 2m23s. N = 8 richiederebbe alcune settimane.


3
Questo è meraviglioso! Fammi sapere se desideri aggiungerlo all'OEIS, il che potrebbe comportare il tuo abbattimento, o se desideri che lo aggiunga.
Peter Kagey,

@PeterKagey Non esitate a aggiungerlo (:
Grimmy,

Affascinante funzione check_symmetry. Potresti per favore dare una breve spiegazione perché non ho familiarità con l'approccio?
John Rees,

1
@JohnRees Verifica semplicemente che la scheda corrente è lessicograficamente ≤ rispetto a tutte le sue simmetrie. Pertanto, in ogni set di schede simmetriche, viene contato esattamente uno: il minimo lessicografico.
Grimmy,

Per fare meglio dell'enumerazione delle soluzioni una per una, è necessario un tipo di meet-in-the-middle. Il problema è che non sembra esserci alcun modo di dividere le cose che ottiene un clustering significativo. Ad esempio, usando lo stesso ordinamento canonico di questa risposta, posizionando 3 esomini ottengo una media di circa 3,7 set di hexominos per maschera. Concludo che è improbabile che questo approccio sia battuto.
Peter Taylor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.