Número de inclinações distintas de um quadrado n X n com n-poliomatinos livres

17

A mais nova sequência OEIS "agradável" , A328020 , foi publicada há alguns minutos atrás.

Número de inclinações distintas de um quadrado n X n com n-poliomatinos livres.

Essa sequência conta inclinações até simetrias do quadrado. A sequência tem seis termos, mas eu gostaria de ver se o pessoal aqui pode estendê-la ainda mais.

Exemplo

Pois n=4existem 22 dessas grades, como mostrado nesta imagem do OEIS. Crédito: Jeff Bowermaster, Ilustração de A328020 (4).A328020 (4)

Desafio

Assim como o desafio anterior , o objetivo desse desafio é calcular o maior número possível de termos nessa sequência, que começa 1, 1, 2, 22, 515, 56734e onde o n-ésimo termo é o número de inclinações da grade n X n com n-poliaminos.

Execute seu código pelo tempo que desejar. O vencedor deste desafio será o usuário que postar mais termos da sequência, junto com seu código para gerá-lo. Se dois usuários postarem o mesmo número de termos, quem quer que publique o último termo ganha mais cedo.

Peter Kagey
fonte
3
Então, isso é simetrias de módulo do quadrado?
Peter Taylor
@ PeterTaylor, isso mesmo. Desambiguei isso na questão.
22826 Peter Kagey
Ingenuamente, eu diria que a entrada n'th levaria number_of_fixed_n_polyominoes ^ ( n operações -1) para calcular. Portanto, para n = 7, seriam necessárias 760 ^ 6 ≈ 2 ^ 57,4 operações. Provavelmente, você pode cortar esse baixo muito, mas é um grande número para começar ...
G. Sliepen
@ Sliepen, espero que você possa reduzir bastante isso apenas retornando. Em particular, existem muitos polinômios fixos que não podem ser colocados no canto, e uma vez que um poliomaino válido é colocado em algum lugar, ele restringe enormemente o que pode ser colocado próximo a ele.
Peter Kagey 8/08/19
@PeterKagey, você está certo. Eu acho que ajuda se, dado que você já colocou m n-poliominoes, você escolhe a próxima posição para tentar colocar um poliomino na pior posição possível, que você pode reduzi-lo bastante.
G. Sliepen 8/10/19

Respostas:

9

Uma extensão do código @ Grimy recebe N = 8

Isso apenas sublinha que o @Grimy merece a recompensa:

Podia podar a árvore de pesquisa estendendo o código para verificar, após cada poliomino concluído, se o espaço livre restante não é particionado em componentes de tamanho não divisível por N.

Em uma máquina em que o código original levou 2m11s para N = 7, isso leva 1m4s e N = 8 foi calculado em 33h46m. O resultado é 23437350133.

Aqui está a minha adição como um 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);

Experimente online!

Peneiradores cristãos
fonte
Isso é muito legal.
Anush
Tudo o que precisamos agora é de uma versão simd multithread :)
#
1
Oh, isso é muito legal! Na verdade, considerei essa otimização, mas não achei que seria suficiente atingir N = 8 em um tempo razoável, por isso não me importei em implementá-la.
Grimmy #
14

C, 7 termos

O sétimo termo é 19846102 . (Os seis primeiros são 1, 1, 2, 22, 515, 56734, conforme declarado na pergunta).

#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);
}

Experimente online! (para N = 6, pois N = 7 expiraria.)

Na minha máquina, N = 6 levou 0,171s e N = 7 levou 2m23s. N = 8 levaria algumas semanas.

Grimmy
fonte
3
Isso é maravilhoso! Deixe-me saber se você gostaria de adicioná-lo ao OEIS - o que pode resultar em você se doendo - ou se você gostaria que eu o adicionasse.
Peter Kagey 9/10/19
@PeterKagey Por favor, sinta-se livre para adicioná-lo (:
Grimmy 10/10
Função check_symmetry fascinante. Poderia, por favor, dar uma breve explicação, pois não estou familiarizado com a abordagem?
John Rees
1
@JohnRees Simplesmente testa se a placa atual é lexicograficamente ≤ a todas as suas simetrias. Assim, em qualquer conjunto de placas simétricas, exatamente uma é contada: o mínimo lexicográfico.
Grimmy
Para fazer melhor do que enumerar as soluções uma a uma, é necessário algum tipo de encontro no meio. O problema é que não parece haver nenhuma maneira de dividir as coisas, o que se torna um agrupamento significativo. Por exemplo, usando a mesma ordem canônica que esta resposta, colocando 3 hexominoes, obtenho uma média de cerca de 3,7 conjuntos de hexominos por máscara. Concluo que é improvável que essa abordagem seja derrotada.
Peter Taylor