Formando Polyominoes con una Cadena de Varillas

20

Antecedentes

Considere una cadena (cerrada) de barras, cada una de las cuales tiene una longitud entera. ¿Cuántos poliominoos distintos sin agujeros puede formar con una cadena dada? O, en otras palabras, ¿cuántos polígonos diferentes que no se cruzan entre sí con lados alineados por eje puede formar con una cadena dada?

Veamos un ejemplo. Considere una cadena particular que consta de 8 barras de longitud 1 y 2, que podemos representar como [1, 1, 2, 2, 1, 1, 2, 2]. Hasta rotaciones y traslaciones, solo hay 8 posibles poliominós (contamos diferentes reflexiones):

ingrese la descripción de la imagen aquí

Esta primera barra es azul oscuro, y luego atravesamos el polígono en sentido antihorario .

El sentido de rotación no afecta el resultado en el ejemplo anterior. Pero consideremos otra cadena [3, 1, 1, 1, 2, 1, 1], que produce los siguientes 3 poliominós:

ingrese la descripción de la imagen aquí

Tenga en cuenta que no incluimos un reflejo del último poliomino, porque requeriría un recorrido en sentido horario.

Si tuviéramos una cadena más flexible de la misma longitud, en [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]realidad podríamos formar ambas reflexiones entre algunas otras poloninoas, con un total de 9:

ingrese la descripción de la imagen aquí

El reto

Dada una descripción de una cadena, como una matriz o similar, determine el número de poliominoes distintos que puede formar (hasta rotaciones y traslaciones) usando las barras en orden mientras recorre el perímetro en sentido antihorario.

Escriba un programa completo e incluya comandos para compilar (si corresponde) y ejecute su código desde la línea de comandos. Incluya también un enlace a un compilador / intérprete gratuito para su idioma.

Su programa debe leer la entrada de STDIN. La primera línea contendrá un número entero M . Las siguientes líneas M serán casos de prueba, cada una de las cuales será una lista de longitudes de varilla separadas por espacios. Su programa debe imprimir líneas M en STDOUT, cada una de las cuales consiste en un único número entero: el número de poliominós distintos que se pueden formar.

Solo debes usar un solo hilo.

Su programa no debe usar más de 1 GB de memoria en cualquier momento. (Este no es un límite completamente estricto, pero monitorearé el uso de memoria de su ejecutable y eliminaré cualquier proceso que use constantemente más de 1 GB o aumente significativamente por encima de él).

Para evitar cantidades excesivas de cálculo previo, su código no debe tener más de 20,000 bytes y no debe leer ningún archivo.

Tampoco debe optimizar hacia los casos de prueba específicos elegidos (por ejemplo, codificando sus resultados). Si sospecho que sí, me reservo el derecho de generar nuevos conjuntos de puntos de referencia. Los conjuntos de prueba son aleatorios, por lo que el rendimiento de su programa en esos debe ser representativo de su rendimiento en la entrada arbitraria. La única suposición que se le permite hacer es que la suma de las longitudes de las barras es uniforme.

Puntuación

He proporcionado conjuntos de referencia para cadenas de N = 10, 11, ..., 20 barras. Cada conjunto de prueba contiene 50 cadenas aleatorias con longitudes entre 1 y 4 inclusive.

Su puntaje principal es la N más grande para la cual su programa completa el conjunto de pruebas completo en 5 minutos (en mi máquina, en Windows 8). El desempate será el tiempo real empleado por su programa en ese conjunto de prueba.

Si alguien supera el conjunto de prueba más grande, seguiré agregando los más grandes.

Casos de prueba

Puede usar los siguientes casos de prueba para verificar la corrección de su implementación.

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

Encontrará un archivo de entrada con estos aquí .

Tabla de clasificación

   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
Martin Ender
fuente
"sin agujeros" parece superfluo. una cadena contigua no puede producir polyominoes perforados en primer lugar.
Sparr
¿Está permitido el subprocesamiento múltiple? Y si los hilos están en procesos diferentes, ¿cada uno puede usar 1 GB? : P
feersum
@Sparr Puede cuando el perímetro se toca en una esquina. Por ejemplo, vea el No. 81 aquí. Ese no debe contarse.
Martin Ender
@feersum Para simplificar, voy a decir que no a los subprocesos múltiples (y editaré el desafío).
Martin Ender
1
@PeterKagey ¿Publicó este comentario sobre el desafío incorrecto? Parece que debería haber ido en este caso .
Martin Ender

Respuestas:

5

C ++ 11

Actualizaciones: se agregó la primera línea cque comienza temprano si la distancia está demasiado lejos del origen (que era el propósito de la variable rlen, pero olvidé escribirla en la primera versión). Lo cambié para usar mucho menos memoria, pero a costa del tiempo. Ahora resuelve N = 20 en poco menos de 5 minutos para mí.

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

Compilar con

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe
Feersum
fuente
doze #defines tho
Soham Chowdhury
En ausencia de otras respuestas ... ¡aquí, ten una recompensa!
Sp3000
3

Python 3 (con 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))

Corre con ./pypy <filename>.


Esta es la implementación de referencia que escribí cuando discutí la pregunta con Martin. No se hizo teniendo en cuenta la velocidad y es bastante hacky, pero debería proporcionar una buena base para comenzar.

N = 18 toma aproximadamente 2.5 minutos en mi modesta computadora portátil.

Algoritmo

Las rotaciones se comprueban convirtiendo cada forma en una serie de Fhacia adelante, Ahacia la izquierda y Chacia la derecha en cada punto de la red en el límite de la forma. A esto le llamo una cadena de ángulo . Dos formas son rotacionalmente idénticas si sus cadenas angulares son permutaciones cíclicas. En lugar de verificar siempre esto comparando dos cadenas de ángulos directamente, cuando encontramos una nueva forma, la convertimos a una forma canónica antes de almacenarla. Cuando tenemos un nuevo candidato, convertimos a la forma canónica y verificamos si ya tenemos esto (explotando el hash, en lugar de iterar a través de todo el conjunto).

La cuerda angular también se usa para verificar que la forma se forme en sentido antihorario, asegurándose de que el número de As exceda el número de Cs en 4.

La auto-intersección se verifica ingenuamente almacenando cada punto de la red en el límite de la forma y viendo si un punto se visita dos veces.

El algoritmo central es simple: coloca la primera barra a la derecha y luego prueba todas las posibilidades para las barras restantes. Si las barras alcanzan un punto que está demasiado lejos del origen (es decir, la suma de las longitudes de barra restantes es menor que la distancia de Manhattan del punto desde el origen), entonces dejamos de buscar ese subárbol prematuramente.

Actualizaciones (las últimas primero)

  • 6/12: Se introdujo la forma canónica, se agregaron algunas micro optimizaciones
  • 5/12: error fijo en la explicación del algoritmo. Hice lineal el algoritmo de verificación de permutación cíclica cuadrática usando las permutaciones cíclicas A, B si una subcadena del método B + B (no tengo idea de por qué no hice esto antes).
Sp3000
fuente