Formando poliaminos com uma corrente de hastes


20

fundo

Considere uma cadeia de hastes (fechada), cada uma com comprimento inteiro. Quantos poliaminos distintos sem orifícios você pode formar com uma determinada corrente? Ou, em outras palavras, quantos polígonos diferentes que não se interceptam com lados alinhados ao eixo você pode formar com uma determinada cadeia?

Vejamos um exemplo. Considere uma corrente específica que consiste em 8 hastes de comprimento 1 e 2, que podemos representar como [1, 1, 2, 2, 1, 1, 2, 2]. Até rotações e traduções, existem apenas 8 possíveis poliomatos (contamos diferentes reflexões):

insira a descrição da imagem aqui

Essa primeira haste é azul escuro e, em seguida, atravessamos o polígono no sentido anti-horário .

O senso de rotação não afeta o resultado no exemplo acima. Mas vamos considerar outra cadeia [3, 1, 1, 1, 2, 1, 1], que produz os seguintes 3 polioquinós:

insira a descrição da imagem aqui

Observe que não incluímos um reflexo do último poliomino, porque isso exigiria a rotação no sentido horário.

Se tivéssemos uma cadeia mais flexível do mesmo comprimento, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]poderíamos realmente formar ambas as reflexões entre alguns outros polioninos, totalizando 9:

insira a descrição da imagem aqui

O desafio

Dada a descrição de uma cadeia, como uma matriz ou similar, determine o número de poliomatos distintos que você pode formar (até rotações e translações) usando as hastes em ordem enquanto percorre o perímetro no sentido anti-horário.

Escreva um programa completo e inclua comandos para compilar (se aplicável) e executar seu código na linha de comando. Inclua também um link para um compilador / intérprete gratuito para o seu idioma.

Seu programa deve ler a entrada do STDIN. A primeira linha conterá um número inteiro M . As próximas M linhas serão casos de teste, cada um dos quais com uma lista de comprimentos de haste separados por espaço. Seu programa deve imprimir linhas M em STDOUT, cada uma das quais consiste em um único número inteiro - o número de poliomatos diferentes que podem ser formados.

Você deve usar apenas um único thread.

Seu programa não deve usar mais de 1 GB de memória a qualquer momento. (Esse não é um limite completamente estrito, mas monitorarei o uso da memória do executável e eliminarei qualquer processo que use consistentemente mais de 1 GB ou que atinja significativamente mais do que isso.)

Para evitar quantidades excessivas de pré-cálculo, seu código não deve exceder 20.000 bytes e você não deve ler nenhum arquivo.

Você também não deve otimizar para os casos de teste específicos escolhidos (por exemplo, codificando seus resultados). Se suspeitar que sim, reservo-me o direito de gerar novos conjuntos de benchmarks. Os conjuntos de testes são aleatórios; portanto, o desempenho do seu programa nesses deve ser representativo pelo desempenho em entradas arbitrárias. A única suposição que você pode fazer é que a soma dos comprimentos da haste seja uniforme.

Pontuação

Forneci conjuntos de referência para cadeias de N = 10, 11, ..., 20 hastes. Cada conjunto de testes contém 50 cadeias aleatórias com comprimentos entre 1 e 4, inclusive.

Sua pontuação principal é o maior N para o qual seu programa conclui todo o conjunto de testes em 5 minutos (na minha máquina, no Windows 8). O desempate será o tempo real gasto pelo seu programa nesse conjunto de testes.

Se alguém vencer o maior conjunto de testes, continuarei adicionando outros maiores.

Casos de teste

Você pode usar os seguintes casos de teste para verificar a correção da sua implementação.

Input                            Output

1 1                              0
1 1 1 1                          1
1 1 1 1 1 1                      1
1 1 1 1 1 1 1 1                  3
1 1 1 1 1 1 1 1 1 1              9
1 1 1 1 1 1 1 1 1 1 1 1          36
1 1 1 1 1 1 1 1 1 1 1 1 1 1      157
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  758
1 1 2 2 1 1 2 2                  8
1 1 2 2 1 1 2 2 1 1              23
1 1 2 2 1 1 2 2 1 1 2 2          69
1 2 1 2 1 2 1 2                  3
1 2 1 2 1 2 1 2 1 2 1 2          37
1 2 3 2 1 2 3 2                  5
1 2 3 2 1 2 3 2 1 2 3 2          23
3 1 1 1 2 1 1                    3
1 2 3 4 5 6 7                    1
1 2 3 4 5 6 7 8                  3
1 2 3 4 5 6 7 8 9 10 11          5
2 1 5 3 3 2 3 3                  4
4 1 6 5 6 3 1 4                  2
3 5 3 5 1 4 1 1 3                5
1 4 3 2 2 5 5 4 6                4
4 1 3 2 1 2 3 3 1 4              18
1 1 1 1 1 2 3 3 2 1              24
3 1 4 1 2 2 1 1 2 4 1 2          107
2 4 2 4 2 2 3 4 2 4 2 3          114

Você encontra um arquivo de entrada com estes aqui .

Entre os melhores

   User          Language       Max N      Time taken (MM:SS:mmm)

1. feersum       C++ 11         19         3:07:430

2. Sp3000        Python 3       18         2:30:181

"sem buracos" parece supérfluo. uma cadeia contígua não pode produzir poliaminoinos perfurados em primeiro lugar.
Sparr

É permitido o multiencadeamento? E se os threads estiverem em processos diferentes, cada um deles usa 1 GB? : P
feersum 04/12/2014

@Sparr Pode quando o perímetro se toca em um canto. Por exemplo, veja o número 81 aqui. Esse não deve ser contado.
Martin Ender

@feersum Por simplicidade, vou dizer não ao multi-threading (e editarei o desafio).
Martin Ender

1
@PeterKagey Você postou este comentário sobre o desafio errado? Parece que deveria ter sido neste .
Martin Ender

Respostas:


5

C ++ 11

Atualizações: adicionada a primeira linha cque sai mais cedo se a distância estiver muito longe da origem (que era o objetivo da variável rlen, mas eu esqueci de escrevê-la na primeira versão). Eu mudei para usar muito menos memória, mas com o custo do tempo. Agora resolve N = 20 em menos de 5 minutos para mim.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <ctime>

#define M std::map
#define MS 999
#define l (xM*2+1)

#define KITTENS(A,B)A##B
#define CATS(A,B)KITTENS(A,B)
#define LBL CATS(LBL,__LINE__)
#define unt unsigned
#define SU sizeof(unt)
#define SUB (SU*8)
#define newa (nb^SZ||fail("blob"),nb+++blob)

#define D

struct vec {int x, y;};


unt s[MS*2];
int xM, sl[MS];
vec X[MS];

struct a;
struct a  { M<unt,unt>v;};

#define SZ ((1<<29)/sizeof(a))
a*blob;
unt nb;


int fail(const char*msg)
{
    printf("failed:%s", msg);
    exit(1);
    return 1;
}

struct
{
    unt*m;
    bool operator()(int x, int y) { return m[(x+l*y)/SUB] >> (x+l*y)%SUB & 1; }
    void one(int x, int y) { m[(x+l*y)/SUB] |= 1U << (x+l*y)%SUB; }
    void zero(int x, int y) { m[(x+l*y)/SUB] &= ~(1U << (x+l*y)%SUB); }
} g;

unt c(a*A, vec x, unt rlen, unt sn) {
    if((unt)x.y+abs(x.x) > rlen) return 0;
    if(!rlen) {
        vec *cl=X, *cr=X, *ct=X;
        for(unt i=1; i<sn; i++) {
            #define BLAH(Z,A,B,o,O) \
                if(X[i].A o Z->A || (X[i].A == Z->A && X[i].B O Z->B)) \
                   Z = X+i

            BLAH(cl,x,y,<,>);
            BLAH(cr,x,y,>,<);
            BLAH(ct,y,x,>,>);
        }
        unt syms = 1;
        #define BLA(H,Z) {bool sy=1;for(unt o=0; o<sn; o++) sy &= (int)(1|-(H))*sl[o] == sl[(Z-X+o)%sn]; syms += sy;}
        BLA(~o&1,cl)
        BLA(1,ct)
        BLA(o&1,cr)

        #ifdef D
            //printf("D");for(int i=0;i<sn;i++)printf(" %u",sl[i]);printf("\n");
            if(syms==3) fail("symm");
        #endif

        return syms;
    }
    if(!(x.x|x.y|!sn)) return 0;
    X[sn] = x;

    unt k = 0;
    for(auto it: A->v) {
        int len = it.first;
        bool ve = sn&1;
        int dx = ve?0:len, dy = ve?len:0;

        #define PPCG(O)(x.x O (ve?0:z), x.y O (ve?z:0))
        #define MACR(O) { \
            vec v2 = {x.x O dx, x.y O dy}; \
            if(v2.y<0||(!v2.y&&v2.x<0)||abs(v2.x)>xM||v2.y>xM) \
                goto LBL; \
            for(int z=1; z<=len; z++) \
                if(g PPCG(O)) \
                    goto LBL; \
            for(int z=1; z<=len; z++) \
                g.one PPCG(O); \
            sl[sn] = O len; \
            k += c(blob+it.second, v2, rlen - len, sn+1); \
            for(int z=1; z<=len; z++) \
                g.zero PPCG(O); \
            } LBL: \

    MACR(+);
    MACR(-);
    }

    return k;
}

void stuff(a *n, unt j, unt r, unt len1)
{
    unt t=0;
    for(unt i=j; i<j+r; i++) {
        t += s[i];
        if((int)t > xM || (len1 && t>len1)) break;
        if(len1 && t < len1) continue;
        int r2 = r-(i-j)-1;
        if(r2) {
            unt x;
            if(n->v.count(t))
                x = n->v[t];
            else
                n->v[t] = x = newa - blob;
            stuff(blob+x, i+1, r2, 0);
        } else n->v[t] = -1;
    }
}

int main()
{
    time_t tim = time(0);
    blob = new a[SZ];
    int n;
    scanf("%u",&n);
    while(n--) {
        nb = 0;
        unt ns=0, tl=0;
        while(scanf("%u",s+ns)) {
            tl += s[ns];
            if(++ns==MS) return 1;
            if(getchar() < 11) break;
        }
        xM = ~-tl/2;
        g.m = (unt*)calloc((xM+1)*l/SU + 1,4);

        memcpy(s+ns, s, ns*SU);

        unt ans = 0;
        for(unt len1 = 1; (int)len1 <= xM; len1++) {
            a* a0 = newa;
            for(unt i=0; i<ns; i++)
                stuff(a0, i, ns, len1);
            ans += c(a0, {}, tl, 0);
            for(unt i=0; i<nb; i++)
                blob[i].v.clear();
        }
        printf("%d\n", ans/4);
        free(g.m);
    }

    tim = time(0) - tim;
    printf("time:%d",(int)tim);
    return 0;
}

Ajuntar com

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe

doze #defineé tho
Soham Chowdhury

Na ausência de outras respostas ... aqui, tenha uma recompensa!
Sp3000

3

Python 3 (com PyPy ) - N = 18

ANGLE_COMPLEMENTS = {"A": "C", "F": "F", "C": "A"}
MOVE_ENUMS = {"U": 0, "R": 1, "D": 2, "L": 3}
OPPOSITE_DIR = {"U": "D", "D": "U", "L": "R", "R": "L", "": ""}

def canonical(angle_str):
    return min(angle_str[i:] + angle_str[:i] for i in range(len(angle_str)))

def to_angles(moves):
    """
    Convert a string of UDLR to a string of angles where
      A -> anticlockwise turn
      C -> clockwise turn
      F -> forward
    """

    angles = []

    for i in range(1, len(moves)):
        if moves[i] == moves[i-1]:
            angles.append("F")
        elif (MOVE_ENUMS[moves[i]] - MOVE_ENUMS[moves[i-1]]) % 4 == 1:
            angles.append("C")
        else:
            angles.append("A")

    if moves[0] == moves[len(moves)-1]:
        angles.append("F")
    elif (MOVE_ENUMS[moves[0]] - MOVE_ENUMS[moves[len(moves)-1]]) % 4 == 1:
        angles.append("C")
    else:
        angles.append("A")

    return "".join(angles)

def solve(rods):
    FOUND_ANGLE_STRS = set()

    def _solve(rods, rod_sum, point=(0, 0), moves2=None, visited=None, last_dir=""):
        # Stop when point is too far from origin
        if abs(point[0]) + abs(point[1]) > rod_sum:
            return

        # No more rods, check if we have a valid solution
        if not rods:
            if point == (0, 0):
               angle_str = to_angles("".join(moves2))

               if angle_str.count("A") - angle_str.count("C") == 4:
                   FOUND_ANGLE_STRS.add(canonical(angle_str))

            return

        r = rods.pop(0)

        if not visited:
            visited = set()
            move_dirs = [((r, 0), "R")]
            moves2 = []

        else:
            move_dirs = [((r,0), "R"), ((0,r), "U"), ((-r,0), "L"), ((0,-r), "D")]

        opp_dir = OPPOSITE_DIR[last_dir]

        for move, direction in move_dirs:
            if direction == opp_dir: continue

            new_point = (move[0] + point[0], move[1] + point[1])
            added_visited = set()
            search = True

            for i in range(min(point[0],new_point[0]), max(point[0],new_point[0])+1):
                for j in range(min(point[1],new_point[1]), max(point[1],new_point[1])+1):
                    if (i, j) != point:
                        if (i, j) in visited:
                            search = False

                            for a in added_visited:
                                visited.remove(a)

                            added_visited = set()                            
                            break

                        else:
                            visited.add((i, j))
                            added_visited.add((i, j))

                if not search:
                    break

            if search:
                moves2.append(direction*r)
                _solve(rods, rod_sum-r, new_point, moves2, visited, direction)
                moves2.pop()

            for a in added_visited:
                visited.remove(a)

        rods.insert(0, r)
        return

    _solve(rods, sum(rods))
    return len(FOUND_ANGLE_STRS)

num_rods = int(input())

for i in range(num_rods):
    rods = [int(x) for x in input().split(" ")]
    print(solve(rods))

Corra com ./pypy <filename>.


Esta é a implementação de referência que escrevi ao discutir a questão com Martin. Não foi feito com a velocidade em mente e é bastante hacky, mas deve fornecer uma boa base para iniciar as coisas.

N = 18 leva cerca de 2,5 minutos no meu laptop modesto.

Algoritmo

As rotações são verificadas convertendo cada forma em uma série de Fpara a frente, Apara a curva anti- Chorária e para a rotação horária em cada ponto da rede no limite da forma - eu chamo isso de uma sequência angular . Duas formas são rotacionalmente idênticas se suas seqüências de ângulos forem permutações cíclicas. Em vez de sempre verificar isso comparando duas seqüências de ângulos diretamente, quando encontramos uma nova forma, convertemos em uma forma canônica antes de armazenar. Quando temos um novo candidato, convertemos para a forma canônica e verificamos se já o temos (explorando o hash, em vez de iterar por todo o conjunto).

A sequência de ângulos também é usada para verificar se a forma é formada no sentido anti-horário, certificando-se de que o número de As exceda o número de Cs por 4.

A auto-interseção é verificada ingenuamente, armazenando todos os pontos da treliça nos limites da forma e verificando se um ponto é visitado duas vezes.

O algoritmo principal é simples, colocando a primeira haste à direita e tentando todas as possibilidades para as hastes restantes. Se as hastes atingirem um ponto muito distante da origem (ou seja, a soma dos comprimentos restantes da haste for menor que a distância de Manhattan do ponto da origem), paramos prematuramente a busca nessa subárvore.

Atualizações (mais recentes primeiro)

  • 6/12: Introduzida a forma canônica, adicionadas algumas micro-otimizações
  • 5/12: Corrigido erro na explicação do algoritmo. Tornou o algoritmo quadrático de verificação de permutação cíclica linear usando as permutações cíclicas A, B e a substring do método B + B (não tenho idéia do por que não fiz isso antes).
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.