Former des polyominos avec une chaîne de tiges

20

Contexte

Considérons une chaîne (fermée) de tiges, dont chacune a une longueur entière. Combien de polyominos distincts sans trou pouvez-vous former avec une chaîne donnée? Ou en d'autres termes, combien de polygones différents non auto-entrecroisés avec des côtés alignés sur l'axe pouvez-vous former avec une chaîne donnée?

Regardons un exemple. Considérons une chaîne particulière composée de 8 tiges de longueur 1 et 2, que nous pouvons représenter comme [1, 1, 2, 2, 1, 1, 2, 2]. Jusqu'à rotations et traductions, il n'y a que 8 polyominos possibles (on compte les réflexions différentes):

entrez la description de l'image ici

Cette première tige est bleu foncé, puis nous traversons le polygone dans le sens antihoraire .

Le sens de rotation n'affecte pas le résultat dans l'exemple ci-dessus. Mais considérons une autre chaîne [3, 1, 1, 1, 2, 1, 1], qui donne les 3 polyominos suivants:

entrez la description de l'image ici

Notez que nous ne reflet du dernier polyomino, car cela nécessiterait une traversée dans le sens horaire.

Si nous avions une chaîne plus flexible de la même longueur, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]nous serions en mesure de former les deux réflexions parmi d'autres polyoninoes, totalisant 9:

entrez la description de l'image ici

Le défi

Étant donné la description d'une chaîne, sous forme de tableau ou similaire, déterminez le nombre de polyominos distincts que vous pouvez former (jusqu'à rotations et traductions) en utilisant les tiges dans l'ordre tout en faisant le tour du périmètre dans le sens antihoraire.

Veuillez écrire un programme complet et inclure des commandes pour compiler (le cas échéant) et exécuter votre code à partir de la ligne de commande. Veuillez également inclure un lien vers un compilateur / interprète gratuit pour votre langue.

Votre programme devrait lire l'entrée de STDIN. La première ligne contient un nombre entier M . Les prochaines lignes M seront des cas de test, dont chacun sera une liste de longueurs de tige séparées par des espaces. Votre programme doit imprimer M lignes sur STDOUT, chacune étant constituée d'un seul entier - le nombre de polyominos distincts qui peuvent être formés.

Vous ne devez utiliser qu'un seul thread.

Votre programme ne doit pas utiliser plus de 1 Go de mémoire à la fois. (Ce n'est pas une limite complètement stricte, mais je surveillerai l'utilisation de la mémoire de votre exécutable et tuerai tout processus qui utilise régulièrement plus de 1 Go ou augmente considérablement au-dessus.)

Pour éviter des quantités excessives de pré-calcul, votre code ne doit pas dépasser 20 000 octets et vous ne devez lire aucun fichier.

Vous ne devez pas non plus optimiser en fonction des cas de test spécifiques choisis (par exemple en codant en dur leurs résultats). Si je soupçonne que vous le faites, je me réserve le droit de générer de nouveaux ensembles de référence. Les ensembles de tests sont aléatoires, donc les performances de votre programme sur celles-ci doivent être représentatives de ses performances sur des entrées arbitraires. La seule hypothèse que vous êtes autorisé à faire est que la somme des longueurs de tige est égale.

Notation

J'ai fourni des jeux de référence pour des chaînes de N = 10, 11, ..., 20 tiges. Chaque ensemble de test contient 50 chaînes aléatoires d'une longueur comprise entre 1 et 4 inclus.

Votre score principal est le plus grand N pour lequel votre programme termine l'ensemble de test complet dans les 5 minutes (sur ma machine, sous Windows 8). Le bris d'égalité sera le temps réel pris par votre programme sur cet ensemble de test.

Si quelqu'un bat le plus grand jeu de test, je continuerai à en ajouter de plus grands.

Cas de test

Vous pouvez utiliser les cas de test suivants pour vérifier l'exactitude de votre implémentation.

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

Vous trouverez un fichier d'entrée avec ceux-ci ici .

Classement

   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
la source
"sans trou" semble superflu. une chaîne contiguë ne peut pas produire des polyominos troués en premier lieu.
Sparr
Le multi-threading est-il autorisé? Et si les threads sont dans des processus différents, chacun peut-il utiliser 1 Go? : P
feersum
@Sparr Il le peut lorsque le périmètre se touche dans un coin. Par exemple, voir n ° 81 ici. Celui-là ne devrait pas être compté.
Martin Ender
@feersum Pour plus de simplicité, je vais dire non au multi-threading (et éditerai le challenge).
Martin Ender
1
@PeterKagey Avez-vous posté ce commentaire sur le mauvais défi? On dirait qu'il aurait dû aller sur celui-ci .
Martin Ender

Réponses:

5

C ++ 11

Mises à jour: Ajout de la première ligne cqui éclate tôt si la distance est trop éloignée de l'origine (ce qui était tout le but de la variable rlen, mais j'ai oublié de l'écrire dans la première version). Je l'ai changé pour utiliser beaucoup moins de mémoire, mais au prix du temps. Il résout maintenant N = 20 en un peu moins de 5 minutes pour moi.

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

Compiler avec

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe
feersum
la source
somnoler #definetho
Soham Chowdhury
En l'absence de toute autre réponse ... ici, ayez une prime!
Sp3000
3

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

Courez avec ./pypy <filename>.


Il s'agit de l'implémentation de référence que j'ai écrite lors de la discussion de la question avec Martin. Il n'a pas été conçu avec la vitesse à l'esprit et est assez hacky, mais il devrait fournir une bonne base de départ pour lancer les choses.

N = 18 prend environ 2,5 minutes sur mon modeste ordinateur portable.

Algorithme

Les rotations sont vérifiées en convertissant chaque forme en une série d' Favant, Ade virage dans le sens inverse des aiguilles d'une montre et Cde rotation dans le sens des aiguilles d'une montre à chaque point du réseau sur la limite de la forme - j'appelle cela une chaîne d'angle . Deux formes sont identiques en rotation si leurs chaînes angulaires sont des permutations cycliques. Plutôt que de toujours vérifier cela en comparant directement deux chaînes d'angles, lorsque nous trouvons une nouvelle forme, nous convertissons en une forme canonique avant de la stocker. Lorsque nous avons un nouveau candidat, nous nous convertissons à la forme canonique et vérifions si nous l'avons déjà (exploitant ainsi le hachage, plutôt que d'itérer à travers l'ensemble).

La chaîne d'angle est également utilisée pour vérifier que la forme est formée dans le sens inverse des aiguilles d'une montre, en s'assurant que le nombre de As dépasse le nombre de Cs par 4.

L'auto-intersection est vérifiée naïvement en stockant chaque point de réseau sur la frontière de la forme et en voyant si un point est visité deux fois.

L'algorithme de base est simple, en plaçant la première tige vers la droite, puis en essayant toutes les possibilités pour les barres restantes. Si les tiges atteignent un point trop éloigné de l'origine (c'est-à-dire que la somme des longueurs de tige restantes est inférieure à la distance Manhattan du point depuis l'origine), alors nous arrêtons prématurément la recherche de ce sous-arbre.

Mises à jour (les plus récentes en premier)

  • 6/12: Présentation de la forme canonique, ajout de quelques micro-optimisations
  • 5/12: Correction d'une erreur dans l'explication de l'algorithme. A rendu l'algorithme de vérification de permutation cyclique quadratique linéaire en utilisant les permutations cycliques A, B si une sous-chaîne A de la méthode B + B (je ne sais pas pourquoi je ne l'ai pas fait plus tôt).
Sp3000
la source