Carrelages domino supersoniques

10

Tâche

Écrivez un programme qui lit trois entiers m , n à partir de STDIN ou comme arguments de ligne de commande, imprime tous les pavages possibles d'un rectangle de dimensions m × n par des dominos 2 × 1 et 1 × 2 et enfin le nombre de pavages valides.

Les dominos d'un pavage individuel doivent être représentés par deux tirets ( -) pour les dominos 2 × 1 et deux barres verticales ( |) pour les dominos 1 × 2 . Chaque pavage (y compris le dernier) doit être suivi d'un saut de ligne.

À des fins d'évaluation, vous devez également accepter un indicateur de STDIN ou comme argument de ligne de commande qui fait que votre programme imprime uniquement le nombre de pavages valides, mais pas les pavages lui-même.

Votre programme ne doit pas dépasser 1 024 octets. Il doit fonctionner pour toutes les entrées telles que m × n ≤ 64 .

(Inspiré par Imprimer tous les pavages domino de rectangle 4x6 .)

Exemple

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

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

Notation

Votre score est déterminé par le temps d'exécution de votre programme pour l'entrée 8 8 avec le drapeau défini.

Pour en faire un code plus rapide qu'un défi informatique plus rapide , je vais exécuter toutes les soumissions sur mon propre ordinateur (Intel Core i7-3770, 16 Go de RAM PC3-12800) pour déterminer le score officiel.

Veuillez laisser des instructions détaillées sur la façon de compiler et / ou d'exécuter votre code. Si vous avez besoin d'une version spécifique du compilateur / interprète de votre langue, faites une déclaration à cet effet.

Je me réserve le droit de laisser les soumissions non notées si:

  • Il n'y a pas de compilateur / interprète gratuit (comme dans la bière) pour mon système d'exploitation (Fedora 21, 64 bits).

  • Malgré nos efforts, votre code ne fonctionne pas et / ou produit une sortie incorrecte sur mon ordinateur.

  • La compilation ou l'exécution prennent plus d'une heure.

  • Votre code ou le seul compilateur / interprète disponible contient un appel système rm -rf ~ou quelque chose de tout aussi louche.

Classement

J'ai réévalué toutes les soumissions, en exécutant les compilations et les exécutions en boucle avec 10000 itérations pour la compilation et entre 100 et 10000 itérations pour l'exécution (en fonction de la vitesse du code) et en calculant la moyenne.

Voici les résultats:

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.
Dennis
la source
Pourquoi ne pas en faire un concours GOLF? :(
orlp
2
Si vous aviez suggéré cela dans le bac à sable, je l'aurais peut-être fait. Cela aurait sauvé mon CPU et moi beaucoup de travail ...
Dennis
3
@ kirbyfan64sos D'après ce que je comprends, il n'y a qu'un seul type de domino, que vous pouvez faire pivoter. Si elle est horizontale, il ressemble à ceci: --. Si c'est vertical, c'est deux |, l'un en dessous de l'autre.
Reto Koradi
1
Votre défi n'est pas mauvais. Le problème est que nos meilleurs codeurs sont trop puissants. Ma solution qui vérifie la validité des lignes et des colonnes reste proche de 1 minute pour 6x8.
edc65
1
Je pense que la meilleure stratégie consiste maintenant à utiliser l'assembly et à essayer d'obtenir un fichier binaire de moins de 1024 octets, pour se débarrasser du temps de complication.
jimmy23013

Réponses:

5

C

Une implémentation simple ...

#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 version triche

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

Explication de l'algorithme plus rapide

Il scanne de gauche à droite et maintient l'état d[i][j], où:

  • iest dans [0,m), ce qui signifie la colonne actuelle.
  • jest un vecteur bit de taille n, où le bit serait 1 si la position correspondante dans la colonne iest déjà occupée avant de commencer à travailler sur cette colonne. C'est à dire qu'il est occupé par la moitié droite d'un --.
  • d[i][j] est le nombre total de pavages différents.

Dites ensuite e[i][j]= la somme de l' d[i][k]endroit où vous pouvez placer la base des dominos verticaux kpour former un j. e[i][j]serait le nombre de pavages où chaque bit 1 jest occupé par autre chose que la moitié gauche d'un --. Remplissez-les avec --et vous obtiendrez d[i+1][~j]= e[i][j]. e[m-1][every bit being 1]ou d[m][0]est la réponse finale.

Une implémentation naïve vous donnera la complexité temporelle quelque part près g[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(déjà assez rapide si n = m = 8). Mais vous pouvez au lieu de cela d'abord boucler pour chaque domino possible, et essayer de l'ajouter à chaque mosaïque qui peut avoir ce domino ajouté, et fusionner le résultat dans le tableau d'origine d(comme l'algorithme pour le problème du sac à dos). Et cela devient O (n * 2 ^ n). Et tout le reste sont des détails d'implémentation. L'ensemble du code s'exécute en O (m * n * 2 ^ n).

jimmy23013
la source
@Dennis Vous voudrez probablement lancer un sondage pour le changer.
jimmy23013
@ Dennis Pas sûr que l'augmentation de la taille aurait beaucoup aidé. Bien qu'il augmente considérablement le temps de calcul, il produit également environ 100 fois plus de sortie. Relativement parlant, la quantité de sortie est en fait plus importante.
Reto Koradi
1ère version Exécution: 0,286 s Compilation: 0,053 s Somme: 0,339 s 2e version Exécution: 0,002 s Compilation: 0,061 s Somme: 0,063 s (Qu'est-ce qui vient de se passer ici?)
Dennis
@Dennis Il a utilisé un autre algorithme en O (m * n * 2 ^ n) si le drapeau est positionné.
jimmy23013
1
Exécution: 190 ms Compilation: 68 ms Somme: 258 ms ( -O1semble être le point idéal. J'ai essayé tous les niveaux d'optimisation.)
Dennis
3

C

Après une série d'optimisations, et adapté aux règles modifiées:

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

J'ai commencé à me heurter à la longueur maximale de 1024 caractères, j'ai donc dû réduire quelque peu la lisibilité. Noms de variables beaucoup plus courts, etc.

Instructions de construction:

> gcc -O2 Code.c

Exécuter avec la sortie de solution activée:

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

Exécuter avec uniquement le nombre de solutions:

> ./a.out 8 8 s

Certains commentaires:

  • Avec l'exemple de test plus grand, je souhaite une optimisation maintenant. Bien que mon système soit différent (Mac), autour -O2semble être bon.
  • Le code est devenu plus lent dans le cas où la sortie est générée. Il s'agissait d'un sacrifice conscient pour optimiser le mode "comptage uniquement" et pour réduire la longueur du code.
  • Il y aura quelques avertissements du compilateur en raison des inclusions manquantes et des déclarations externes pour les fonctions système. C'était le moyen le plus simple de me faire descendre en dessous de 1024 caractères sans rendre le code totalement illisible.

Notez également que le code génère toujours les solutions réelles, même en mode "comptage uniquement". Chaque fois qu'une solution est trouvée, le vMmasque de bits contient un 1pour les positions qui ont une barre verticale et un 0pour les positions avec une barre horizontale. Seule la conversion de ce masque binaire au format ASCII et la sortie réelle sont ignorées.

Reto Koradi
la source
@Dennis Nouvelle version. L'exécution devrait être inchangée, mais la compilation plus rapide. Si nous devons optimiser le temps de compilation, nous n'avons pas besoin d'en-têtes système!
Reto Koradi
@Dennis Mis à jour pour de nouveaux scores et pour une série d'optimisations. Notez que je veux une optimisation maintenant, quelque chose comme ça -O2devrait être bon.
Reto Koradi
Exécution: 256 ms Compilation: 65 ms Somme: 321 ms ( -O2semble être le point idéal. J'ai essayé tous les niveaux d'optimisation.)
Dennis
1

C

Le concept est de trouver d'abord tous les arrangements possibles de dominos horizontaux dans une rangée, de les stocker r[]puis de les organiser pour donner tous les arrangements possibles de dominos verticaux.

Le code de positionnement des dominos horizontaux dans une rangée est modifié à partir de ma réponse: https://codegolf.stackexchange.com/a/37888/15599 . C'est lent pour les grilles plus larges mais ce n'est pas un problème pour le boîtier 8x8.

L'innovation réside dans la façon dont les rangées sont assemblées. Si la carte a un nombre impair de lignes, elle est tournée de 90 degrés dans l'analyse en entrée, elle a donc maintenant un nombre pair de lignes. Maintenant, je place des dominos verticaux à travers la ligne médiane. En raison de la symétrie, s'il existe des cmoyens de disposer les dominos restants dans la moitié inférieure, il doit également y avoir des cmoyens de disposer les dominos restants dans la moitié supérieure, ce qui signifie que pour une disposition donnée de dominos verticaux sur la ligne médiane, il existe c*cdes solutions possibles . Par conséquent, seule la ligne médiane plus la moitié de la carte est analysée lorsque le programme doit imprimer uniquement le nombre de solutions.

f()construit le tableau des dispositions possibles des dominos horizontaux et analyse les dispositions possibles des dominos verticaux sur la ligne médiane. il appelle ensuite la fonction récursive g()qui remplit les lignes. Si l'impression est requise, une fonction h()est appelée pour ce faire.

g()est appelé avec 3 paramètres. yest la ligne actuelle et dest la direction (vers le haut ou vers le bas) dans laquelle nous remplissons la planche du centre vers l'extérieur. xcontient un bitmap indique les dominos verticaux qui sont incomplets de la ligne précédente. Toutes les dispositions possibles des dominos consécutifs de r [] sont essayées. Dans ce tableau, un 1 représente un domino vertical et une paire de zéros un domino horizontal. Une entrée valable dans la matrice doit avoir au moins suffisamment de 1 pour terminer tous les dominos verticaux incomplets de la dernière ligne: (x&r[j])==x. Il peut y avoir plus de 1, ce qui indique que de nouveaux dominos verticaux sont en cours de démarrage. Pour la ligne suivante, nous n'avons besoin que des nouveaux dominos, nous appelons donc à nouveau la procédure avec x^r[j].

Si une rangée de fin a été atteinte et qu'il n'y a pas de dominos verticaux incomplets suspendus en haut ou en bas de la planche, x^r[j]==0la moitié a été complétée avec succès. Si nous n'imprimons pas, il suffit de compléter la moitié inférieure et d'utiliser c*cpour calculer le nombre total d'arrangements. Si nous imprimons, il sera nécessaire de compléter également la moitié supérieure, puis d'appeler la fonction d'impression h().

CODE

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");

}

Notez que l'entrée avec un nombre impair de lignes et un nombre pair de colonnes est tournée de 90 degrés dans la phase d'analyse. Si cela est inacceptable, la fonction d'impression h()peut être modifiée pour l'adapter. (EDIT: non requis, voir les commentaires.)

EDIT: Une nouvelle fonction e()a été utilisée pour vérifier la parité de i(c'est-à-dire le nombre de dominos chevauchant la ligne médiane). La parité de i(le nombre de demi-dominos sur la ligne médiane faisant saillie dans chaque moitié de la planche) doit être la même que la bizarrerie du nombre total d'espaces dans chaque moitié (donnée par n/2) car alors seulement les dominos peuvent remplir tout l'espace disponible. Cette modification élimine la moitié des valeurs de i et rend donc mon programme environ deux fois plus rapide.

Level River St
la source
Exécution: 18 ms Compilation: 50 ms Somme: 68 ms ( -O0était le point idéal pour le total. D'autres options ont ralenti la compilation.)
Dennis
Cela ne se termine jamais, ou prend au moins très longtemps, pour l'entrée 32 2 s. Je l'ai arrêté après environ 15 minutes.
Reto Koradi
@RetoKoradi en effet, 2 32 sfonctionne pourtant presque instantanément. La numérisation à travers tous les dominos verticaux possibles est extrêmement inutile pour le H=2boîtier, car en fait, nous avons déjà toutes les informations nécessaires r[]. Je suis très satisfait de l'heure officielle de 8 8 sVoici un patch pour le cas que vous mentionnez: 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;Comme vous pouvez le voir, cet extrait sera exécuté instantanément H=2 avec le jeu de drapeaux. Le temps d'exécution global est alors limité par le bâtiment r[]qui a certainement des améliorations à apporter.
Level River St
Pour être complet, voici le correctif pour augmenter la sortie dans le bon sens, si nécessaire: 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);longueur de code est toujours bien inférieure à 1 000 octets et l'impact sur le temps de compilation devrait être minime. Je n'ai pas inclus ces patchs hier soir car j'étais trop fatigué.
Level River St
Je voulais faire un commentaire à ce sujet hier soir, mais j'ai oublié. Puisque le pointage est fait sur un carré, je ne vais pas insister sur un ordre particulier.
Dennis