Soffitti a domino supersonici


10

Compito

Scrivere un programma che legge tre numeri interi m , n da STDIN o come argomenti della riga di comando, stampa tutti i possibili tasselli di un rettangolo di dimensioni m × n con domini 2 × 1 e 1 × 2 e infine il numero di massimali validi.

I domini di una singola piastrellatura devono essere rappresentati da due trattini ( -) per 2 × 1 e due barre verticali ( |) per domini 1 × 2 . Ogni piastrellatura (compresa l'ultima) deve essere seguita da un avanzamento riga.

Ai fini del punteggio, è inoltre necessario accettare un flag da STDIN o come argomento della riga di comando che consente al programma di stampare solo il numero di tasselli validi, ma non i tasselli stessi.

Il programma non può superare i 1024 byte. Deve funzionare per tutti gli ingressi in modo tale che m × n ≤ 64 .

(Ispirato da Stampa tutti i soffitti domino del rettangolo 4x6 .)

Esempio

$ sdt 4 2
----
----

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

punteggio

Il tuo punteggio è determinato dal tempo di esecuzione del tuo programma per l'ingresso 8 8 con il flag impostato.

Per rendere questo un codice più veloce piuttosto che una sfida al computer più veloce , eseguirò tutti gli invii sul mio computer (Intel Core i7-3770, 16 GiB PC3-12800 RAM) per determinare il punteggio ufficiale.

Si prega di lasciare istruzioni dettagliate su come compilare e / o eseguire il codice. Se hai bisogno di una versione specifica del compilatore / interprete della tua lingua, fai una dichiarazione in tal senso.

Mi riservo il diritto di lasciare le iscrizioni senza punteggio se:

  • Non esiste un compilatore / interprete gratuito (come nella birra) per il mio sistema operativo (Fedora 21, 64 bit).

  • Nonostante i nostri sforzi, il tuo codice non funziona e / o produce un output errato sul mio computer.

  • La compilazione o l'esecuzione richiede più di un'ora.

  • Il tuo codice o il solo compilatore / interprete disponibile contiene una chiamata di sistema rm -rf ~o qualcosa di altrettanto complicato.

Classifica

Ho riclassificato tutti gli invii, eseguendo sia compilazioni che esecuzioni in un ciclo con 10.000 iterazioni per la compilazione e tra 100 e 10.000 iterazioni per l'esecuzione (a seconda della velocità del codice) e calcolando la media.

Questi erano i risultati:

User          Compiler   Score                              Approach

jimmy23013    GCC (-O0)    46.11 ms =   1.46 ms + 44.65 ms  O(m*n*2^n) algorithm.
steveverrill  GCC (-O0)    51.76 ms =   5.09 ms + 46.67 ms  Enumeration over 8 x 4.
jimmy23013    GCC (-O1)   208.99 ms = 150.18 ms + 58.81 ms  Enumeration over 8 x 8.
Reto Koradi   GCC (-O2)   271.38 ms = 214.85 ms + 56.53 ms  Enumeration over 8 x 8.

Perché non rendere questo un concorso GOLF? :(
orlp

2
Se me lo avessi suggerito nella sandbox, avrei potuto. Ciò avrebbe risparmiato un sacco di lavoro alla mia CPU e a me ...
Dennis,

3
@ kirbyfan64sos Per come lo capisco, c'è solo un tipo di domino, che puoi ruotare. Se è orizzontale, sembra che questo: --. Se è verticale, è due |, uno sotto l'altro.
Reto Koradi,

1
La tua sfida non è male. Il problema è che i nostri migliori programmatori sono troppo forti. La mia soluzione che controlla la validità di righe e colonne rimane vicino a 1 minuto per 6x8.
edc65,

1
Penso che la migliore strategia ora sia usare assembly e provare a ottenere un file binario inferiore a 1024 byte, per sbarazzarsi del tempo di complicazione.
jimmy23013,

Risposte:


5

C

Un'implementazione semplice ...

#include<stdio.h>
int a,b,c,s[65],l=0,countonly;
unsigned long long m=0;
char r[100130];
void w(i,x,o){
    int j,k;
    j=(1<<b)-1&~x;
    if(i<a-1){
        s[i+1]=j;
        w(i+1,j,1);
        for(k=j&j/2&-o;j=k&-k;k&=~j)
            w(i,x|3*j,j);
    }
    else if((j/3|j/3*2)==j)
        if(countonly)
            m++;
        else{
            if(c==b)
                for(k=0;k<b;r[k++,l++]=10)
                    for(j=0;j<a;j++)
                        r[l++]=45+79*!((s[j]|s[j+1])&(1<<k));
            else
                for(j=0;j<a;r[j++,l++]=10)
                    for(k=0;k<b;k++)
                        r[l++]=124-79*!((s[j]|s[j+1])&(1<<k));
            r[l++]=10;
            if(l>=100000){
                fwrite(r,l,1,stdout);
                l=0;
            }
        }
}

int main(){
    scanf("%d %d %d",&a,&b,&countonly);
    c=b;
    if(a<b){a^=b;b^=a;a^=b;}
    s[0]=s[a]=0;
    w(0,0,1);
    if(countonly)
        printf("%llu\n",m);
    else if(l)
        fwrite(r,l,1,stdout);
}

La versione traditrice

#include<stdio.h>
#include<string.h>
int a,b,c,s[65],l=0,countonly;
unsigned long long m=0,d[256];
char r[100130];
void w2(){
    int i,j,k,x;
    memset(d,0,sizeof d);
    d[0]=1;
    j=0;
    for(i=0;i<a-1;i++){
        for(k=1;k<(1<<(b-1));k*=2)
            for(x=0;x<(1<<(b-2));x++)
                d[(x+x/k*k*3+k*3)^j]+=d[(x+x/k*k*3)^j];
        j^=(1<<b)-1;
    }
    for(x=0;x<(1<<b);x++)
        if((x/3|x/3*2)==x)
            m+=d[x^((1<<b)-1)^j];
    printf("%llu\n",m);
}

void w(i,x,o){
    int j,k;
    j=(1<<b)-1&~x;
    if(i<a-1){
        s[i+1]=j;
        w(i+1,j,1);
        for(k=j&j/2&-o;j=k&-k;k&=~j)
            w(i,x|3*j,j);
    }
    else if((j/3|j/3*2)==j){
        if(c==b)
            for(k=0;k<b;r[k++,l++]=10)
                for(j=0;j<a;j++)
                    r[l++]=45+79*!((s[j]|s[j+1])&(1<<k));
        else
            for(j=0;j<a;r[j++,l++]=10)
                for(k=0;k<b;k++)
                    r[l++]=124-79*!((s[j]|s[j+1])&(1<<k));
        r[l++]=10;
        if(l>=100000){
            fwrite(r,l,1,stdout);
            l=0;
        }
    }
}

int main(){
    scanf("%d %d %d",&a,&b,&countonly);
    c=b;
    if(a<b){a^=b;b^=a;a^=b;}
    s[0]=s[a]=0;
    if(countonly)
        w2();
    else{
        w(0,0,1);
        if(l)
            fwrite(r,l,1,stdout);
    }
}

Spiegazione dell'algoritmo più veloce

Esegue la scansione da sinistra a destra e mantiene lo stato d[i][j], dove:

  • iè in [0,m), il che significa che la colonna corrente.
  • jè un vettore di bit di dimensione n, dove il bit sarebbe 1 se la posizione corrispondente nella colonna iè già occupata prima di iniziare a lavorare su questa colonna. Cioè è occupato dalla metà destra di a --.
  • d[i][j] è il numero totale di diversi massimali.

Quindi dire e[i][j]= la somma di d[i][k]dove è possibile posizionare la base del domino verticale kper formare un j. e[i][j]sarebbe il numero di controsoffitti in cui ogni 1 bit jè occupato da qualsiasi cosa tranne la metà sinistra di a --. Riempili con --e otterrai d[i+1][~j]= e[i][j]. e[m-1][every bit being 1]o d[m][0]è la risposta finale.

Un'implementazione ingenua ti porterà la complessità del tempo da qualche parte vicino g[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(già abbastanza veloce se n = m = 8). Ma puoi invece prima eseguire il ciclo per ogni possibile domino e provare ad aggiungerlo a ogni piastrellatura a cui è possibile aggiungere questo domino e unire il risultato all'array originale d(come l'algoritmo per il problema dello zaino). E questo diventa O (n * 2 ^ n). E tutto il resto sono dettagli di implementazione. L'intero codice viene eseguito in O (m * n * 2 ^ n).


@Dennis Probabilmente vuoi iniziare un sondaggio per cambiarlo.
jimmy23013

@Dennis Non sono sicuro che aumentare le dimensioni avrebbe aiutato molto. Mentre aumenta sostanzialmente il tempo di calcolo, produce anche circa 100 volte più output. Relativamente parlando, la quantità di output è in realtà maggiore.
Reto Koradi,

Prima versione Esecuzione: 0,286 s Compilazione: 0,053 s Somma: 0,339 s Seconda versione Esecuzione: 0,002 s Compilazione: 0,061 s Somma: 0,063 s (Che cosa è successo qui?)
Dennis

@Dennis Ha usato un altro algoritmo in O (m * n * 2 ^ n) se il flag è impostato.
jimmy23013,

1
Esecuzione: 190 ms Compilazione: 68 ms Somma: 258 ms ( -O1sembra essere il punto debole. Ho provato tutti i livelli di ottimizzazione.)
Dennis

3

C

Dopo una serie di ottimizzazioni e adattato alle regole modificate:

typedef unsigned long u64;

static int W, H, S;
static u64 RM, DM, NSol;
static char Out[64 * 2 + 1];

static void place(u64 cM, u64 vM, int n) {
  if (n) {
    u64 m = 1ul << __builtin_ctzl(cM); cM -= m;

    if (m & RM) {
      u64 nM = m << 1;
      if (cM & nM) place(cM - nM, vM, n - 1);
    }

    if (m & DM) {
      u64 nM = m << W;
      vM |= m; vM |= nM; place(cM - nM, vM, n - 1);
    }
  } else if (S) {
    ++NSol;
  } else {
    char* p = Out;
    for (int y = 0; y < H; ++y) {
      for (int x = 0; x < W; ++x) { *p++ = vM & 1 ? '|' : '-'; vM >>= 1; }
      *p++ = '\n';
    }
    *p++ = '\0';
    puts(Out);
    ++NSol;
  }
}

int main(int argc, char* argv[]) {
  W = atoi(argv[1]); H = atoi(argv[2]); S = (argc > 3);

  int n = W * H;
  if (n & 1) return 0;

  for (int y = 0; y < H; ++y) {
    RM <<= W; RM |= (1ul << (W - 1)) - 1;
  }
  DM = (1ul << (W * (H - 1))) - 1;

  place(-1, 0, n >> 1);
  printf("%lu\n", NSol);

  return 0;
}

Ho iniziato a superare il limite di lunghezza di 1024 caratteri, quindi ho dovuto ridurre in qualche modo la leggibilità. Nomi di variabili molto più brevi, ecc.

Istruzioni per la costruzione:

> gcc -O2 Code.c

Esegui con l'output della soluzione abilitato:

> ./a.out 8 8 >/dev/null

Esegui con solo il conteggio delle soluzioni:

> ./a.out 8 8 s

Alcuni commenti:

  • Con l'esempio di test più ampio, ora voglio l'ottimizzazione. Mentre il mio sistema è diverso (Mac), in giro -O2sembra essere buono.
  • Il codice è diventato più lento nel caso in cui viene generato l'output. Questo è stato un sacrificio consapevole per l'ottimizzazione della modalità "conta solo" e per ridurre la lunghezza del codice.
  • Ci saranno alcuni avvisi del compilatore a causa di inclusioni mancanti e dichiarazioni esterne per le funzioni di sistema. Alla fine è stato il modo più semplice per portarmi a meno di 1024 caratteri senza rendere il codice totalmente illeggibile.

Si noti inoltre che il codice genera ancora le soluzioni effettive, anche in modalità "conta solo". Ogni volta che viene trovata una soluzione, la vMmaschera di bit contiene a 1per le posizioni che hanno una barra verticale e a 0per le posizioni con una barra orizzontale. Viene ignorata solo la conversione di questa maschera di bit in formato ASCII e l'output effettivo.


@Dennis Nuova versione. L'esecuzione dovrebbe essere invariata, ma la compilazione più veloce. Se dobbiamo ottimizzare per il tempo di compilazione, non abbiamo bisogno di intestazioni di sistema!
Reto Koradi,

@Dennis Aggiornato per il nuovo punteggio e per una serie di ottimizzazioni. Nota che ora voglio l'ottimizzazione, qualcosa del genere -O2dovrebbe essere buono.
Reto Koradi,

Esecuzione: 256 ms Compilazione: 65 ms Somma: 321 ms ( -O2sembra essere il punto debole. Ho provato tutti i livelli di ottimizzazione.)
Dennis

1

C

L'idea è quella di trovare prima tutte le possibili disposizioni dei domino orizzontali di seguito, memorizzarli r[]e quindi organizzarli per dare tutte le possibili disposizioni dei domini verticali.

Il codice per posizionare i domino orizzontali di fila è modificato da questa mia risposta: https://codegolf.stackexchange.com/a/37888/15599 . È lento per le griglie più larghe ma non è un problema per il caso 8x8.

L'innovazione sta nel modo in cui le file sono assemblate. Se la scheda ha un numero dispari di righe, viene ruotata di 90 gradi nell'analisi di input, quindi ora ha un numero pari di righe. Ora posiziono dei domino verticali lungo la linea centrale. A causa della simmetria, se ci sonoc modi per disporre i domino rimanenti nella metà inferiore, ci devono essere anche cmodi per disporre i domino rimanenti nella metà superiore, il che significa che per una data disposizione di domino verticali sulla linea centrale, ci sono c*cpossibili soluzioni . Pertanto, viene analizzata solo la mezzeria più la metà della scheda quando il programma è tenuto a stampare solo il numero di soluzioni.

f()costruisce la tabella delle possibili disposizioni dei domino orizzontali e scansiona le possibili disposizioni dei domino verticali sulla linea centrale. quindi chiama la funzione ricorsivag() che riempie le righe. Se è richiesta la stampa, h()viene chiamata funzione per fare ciò.

g()viene chiamato con 3 parametri. yè la riga corrente ed dè la direzione (su o giù) in cui stiamo riempiendo la scheda dal centro verso l'esterno. xcontiene una bitmap indica i domino verticali che sono incompleti dalla riga precedente. Vengono provate tutte le possibili disposizioni di domino di fila da r []. In questo array, un 1 rappresenta un domino verticale e una coppia di zero un domino orizzontale. Una voce valida nella matrice deve avere almeno sufficiente 1 di per finire eventuali domino verticali incompleti dall'ultima fila: (x&r[j])==x. Potrebbe avere più 1 che indica che sono stati avviati nuovi domino verticali. Per la riga successiva, quindi, abbiamo bisogno solo dei nuovi domino, quindi chiamiamo di nuovo la procedura con x^r[j].

Se è stata raggiunta una riga finale e non ci sono domini verticali incompleti sospesi nella parte superiore o inferiore della tavola, x^r[j]==0la metà è stata completata con successo. Se non stiamo stampando, è sufficiente completare la metà inferiore e utilizzare c*cper calcolare il numero totale di arrangiamenti. Se stiamo stampando, sarà necessario completare anche la metà superiore e quindi chiamare la funzione di stampa h().

CODICE

unsigned int W,H,S,n,k,t,r[1<<22],p,q[64];
long long int i,c,C;


//output: ascii 45 - for 0, ascii 45+79=124 | for 1
h(){int a;
  for(a=n;a--;a%W||puts(""))putchar(45+(q[a/W]>>a%W)%2*79);
  puts("");
}

g(y,d,x){int j;
  for(j=0;j<p;j++)if((x&r[j])==x){
    q[y]=r[j];
    if(y%(H-1)==0){
       (x^r[j])==0?
        y?c++,S||g(H/2-1,-1,i):h() 
       :0;
    }else{
      g(y+d,d,x^r[j]);
    }

  }    
}

e(z){int d;
  for(d=0;z;d++)z&=z-1;return n/2+1+d&1; 
}

f(){
  //k=a row full of 1's
  k=(1ULL<<W)-1;

  //build table of possible arrangements of horizontal dominoes in a row;
  //flip bits so that 1=a vertical domino and save to r[]
  for(i=0;i<=k;i++)(i/3|i/3<<1)==i?r[p++]=i^k:0;

  //for each arrangement of vertical dominoes on the centreline, call g()
  for(i=0;i<=k;i++)e(i)?c=0,g(H/2,1,i),C+=c*c:0;
  printf("%llu",C);
}


main(int argc, char* argv[]) {
  W = atoi(argv[1]); H = atoi(argv[2]); S = (argc > 3);
  1-(n=W*H)%2?
      H%2?W^=H,H^=W,W^=H,t=1:0,f()
    :puts("0");

}

Si noti che l'input con un numero dispari di righe e un numero pari di colonne viene ruotato di 90 gradi nella fase di analisi. Se ciò è inaccettabile, è h()possibile modificare la funzione di stampa per adattarla. (EDIT: non richiesto, vedi commenti.)

MODIFICA: e()è stata utilizzata una nuova funzione per verificare la parità di i(ovvero il numero di domino a cavallo della linea centrale.) La parità di i(il numero di semi-domino sulla linea centrale che sporge in ciascuna metà della scheda) deve essere uguale a la stranezza del numero totale di spazi in ciascuna metà (data da n/2) perché solo allora i domino possono riempire tutto lo spazio disponibile. Questa modifica elimina la metà dei valori di i e quindi rende il mio programma circa due volte più veloce.


Esecuzione: 18 ms Compilazione: 50 ms Somma: 68 ms ( -O0era il punto debole per il totale. Altre opzioni hanno rallentato la compilazione.)
Dennis

Questo non termina mai, o almeno richiede molto tempo, per l'input 32 2 s. L'ho fermato dopo circa 15 minuti.
Reto Koradi,

@RetoKoradi in effetti, eppure 2 32 scorre quasi all'istante. La scansione di tutti i possibili domino verticali è estremamente dispendiosa per il H=2caso, perché in realtà abbiamo già tutte le informazioni necessarie r[]. Sono molto contento del tempo ufficiale per 8 8 sEcco una patch per il caso che menzioni: if(H==2){C=p;if(!S)for(i=0;i<p;i++)q[0]=q[1]=r[i],h();}else for(i=0;i<=k;i++)c=0,g(H/2,1,i),C+=c*c;come puoi vedere, questo frammento verrà eseguito immediatamente H=2 con il set di flag. Il tempo di esecuzione complessivo è quindi limitato dalla costruzione di r[]cui certamente ha margini di miglioramento.
Level River St

Per completezza, ecco la patch per invertire l'output, se necessario: la if(t)for(a=n;a--;a%H||puts(""))putchar(124-(q[a%H]>>a/H)%2*79);else for(a=n;a--;a%W||puts(""))putchar(45+(q[a/W]>>a%W)%2*79);lunghezza del codice è ancora ben al di sotto di 1000 byte e l'impatto sul tempo di compilazione dovrebbe essere minimo. Non ho incluso queste patch ieri sera perché ero troppo stanco.
Level River St

Volevo commentare quella notte scorsa, ma ho dimenticato. Dal momento che il punteggio viene eseguito su un quadrato, non ho intenzione di insistere su un ordine particolare.
Dennis,
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.