Formazione di poliomino con una catena di aste


20

sfondo

Considera una catena (chiusa) di aste, ognuna delle quali ha una lunghezza intera. Quanti distinti poliomino senza fori puoi formare con una data catena? O in altre parole, quanti diversi poligoni non autointersecanti con lati allineati agli assi puoi formare con una data catena?

Diamo un'occhiata a un esempio. Considera una catena particolare composta da 8 aste di lunghezza 1 e 2, che possiamo rappresentare come [1, 1, 2, 2, 1, 1, 2, 2]. Fino a rotazioni e traduzioni, ci sono solo 8 possibili poliomino (contiamo diverse riflessioni):

inserisci qui la descrizione dell'immagine

Questa prima asta è blu scuro, quindi attraversiamo il poligono in senso antiorario .

Il senso di rotazione non influisce sul risultato nell'esempio sopra. Ma consideriamo un'altra catena [3, 1, 1, 1, 2, 1, 1], che produce i seguenti 3 poliomino:

inserisci qui la descrizione dell'immagine

Si noti che non includiamo un riflesso dell'ultimo poliomino, perché richiederebbe un movimento in senso orario.

Se avessimo una catena più flessibile della stessa lunghezza, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]saremmo effettivamente in grado di formare entrambe le riflessioni tra alcuni altri polyonino, per un totale di 9:

inserisci qui la descrizione dell'immagine

La sfida

Data una descrizione di una catena, come una matrice o simile, determina il numero di poliomino distinti che puoi formare (fino a rotazioni e traslazioni) usando le aste in ordine mentre percorrono il perimetro in senso antiorario.

Scrivi un programma completo e includi i comandi per compilare (se applicabile) ed eseguire il codice dalla riga di comando. Includi anche un link a un compilatore / interprete gratuito per la tua lingua.

Il programma dovrebbe leggere l'input da STDIN. La prima riga conterrà un numero intero M . Le prossime linee M saranno casi di test, ognuno dei quali sarà un elenco separato da spazi di lunghezze di aste. Il tuo programma dovrebbe stampare M righe su STDOUT, ognuna delle quali è costituita da un singolo numero intero - il numero di poliomino distinti che possono essere formati.

Devi usare solo un singolo thread.

Il programma non deve utilizzare più di 1 GB di memoria in qualsiasi momento. (Questo non è un limite del tutto rigoroso, ma monitorerò l'utilizzo della memoria del tuo eseguibile e ucciderò qualsiasi processo che utilizza costantemente più di 1 GB o picchi significativamente al di sopra di esso.)

Per evitare quantità eccessive di pre-calcolo, il codice non deve superare i 20.000 byte e non è necessario leggere alcun file.

Inoltre, non è necessario ottimizzare i casi di test specifici scelti (ad es. Codificandone i risultati). Se sospetto che lo facciate, mi riservo il diritto di generare nuovi set di parametri. I set di test sono casuali, quindi le prestazioni del tuo programma su quelle dovrebbero essere rappresentative per le sue prestazioni su input arbitrari. L'unico presupposto che puoi fare è che la somma delle lunghezze delle aste sia pari.

punteggio

Ho fornito set di parametri di riferimento per catene di N = 10, 11, ..., 20 aste. Ogni set di test contiene 50 catene casuali con lunghezze comprese tra 1 e 4 incluso.

Il tuo punteggio principale è la N più grande per la quale il tuo programma completa l'intero set di test entro 5 minuti (sulla mia macchina, sotto Windows 8). Il tie breaker sarà il tempo effettivo impiegato dal programma su quel set di test.

Se qualcuno batte il set di test più grande, continuerò ad aggiungerne di più grandi.

Casi test

È possibile utilizzare i seguenti casi di test per verificare la correttezza dell'implementazione.

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

Puoi trovare un file di input con questi qui .

Classifica

   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

"senza buche" sembra superfluo. una catena contigua non può produrre in primo luogo poliomino con fori.
Sparr,

Il multi-threading è consentito? E se i thread sono in processi diversi, ognuno può usare 1 GB? : P
feersum,

@Sparr Può quando il perimetro si tocca in un angolo. Ad esempio, vedere il n. 81 qui. Quello non dovrebbe essere contato.
Martin Ender,

@feersum Per semplicità, ho intenzione di dire no al multi-threading (e modificherò la sfida).
Martin Ender,

1
@PeterKagey Hai pubblicato questo commento sulla sfida sbagliata? Sembra che avrebbe dovuto andare su questo .
Martin Ender,

Risposte:


5

C ++ 11

Aggiornamenti: aggiunta la prima riga cche si apre presto se la distanza è troppo lontana dall'origine (che era lo scopo della variabile rlen, ma ho dimenticato di scriverlo nella prima versione). L'ho cambiato per usare molta meno memoria, ma a costo del tempo. Ora risolve N = 20 in poco meno di 5 minuti per me.

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

Compila con

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

doze #defines tho
Soham Chowdhury,

In assenza di altre risposte ... qui, abbi una taglia!
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))

Corri con ./pypy <filename>.


Questa è l'implementazione di riferimento che ho scritto quando ho discusso la domanda con Martin. Non è stato realizzato pensando alla velocità ed è piuttosto confuso, ma dovrebbe fornire una buona base per iniziare.

N = 18 impiega circa 2,5 minuti sul mio laptop modesto.

Algoritmo

Le rotazioni vengono verificate convertendo ciascuna forma in una serie di Fper avanti, Aper la rotazione in senso antiorario e Cper la rotazione in senso orario in corrispondenza di ciascun punto del reticolo sul bordo della forma: chiamo questa stringa angolare . Due forme sono identiche in senso rotazionale se le loro stringhe angolari sono permutazioni cicliche. Piuttosto che verificare sempre questo confrontando direttamente due stringhe angolari, quando troviamo una nuova forma che convertiamo in una forma canonica prima di memorizzarla. Quando abbiamo un nuovo candidato, ci convertiamo nella forma canonica e controlliamo se abbiamo già questo (sfruttando così l'hash, piuttosto che iterando attraverso l'intero set).

La stringa dell'angolo viene anche utilizzata per verificare che la forma sia formata in senso antiorario, assicurandosi che il numero di As superi il numero di Cs di 4.

L'autointersezione viene verificata ingenuamente memorizzando ogni punto reticolare sul bordo della forma e vedendo se un punto viene visitato due volte.

L'algoritmo core è semplice, posizionando la prima barra verso destra, quindi provando tutte le possibilità per le rimanenti aste. Se le aste raggiungono un punto troppo lontano dall'origine (cioè la somma delle lunghezze rimanenti dell'asta è inferiore alla distanza di Manhattan del punto dall'origine), allora interrompiamo prematuramente la ricerca di quella sottostruttura.

Aggiornamenti (prima i più recenti)

  • 6/12: introdotta la forma canonica, aggiunte alcune microottimizzazioni
  • 5/12: errore risolto nella spiegazione dell'algoritmo. Rende lineare l'algoritmo quadratico di controllo della permutazione ciclica usando le permutazioni cicliche A, B se una sottostringa del metodo B + B (non ho idea del perché non l'ho fatto prima).
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.