Bir Çubuk Zinciri ile Polimino Oluşturma


20

Arka fon

Her biri tamsayı uzunluğuna sahip (kapalı) bir çubuk zinciri düşünün. Belirli bir zincirle kaç farklı deliksiz poliomino oluşturabilirsiniz? Veya başka bir deyişle, belirli bir zincirle eksenle hizalanmış kenarları olan, birbiriyle kesişen kaç farklı çokgen oluşturabilirsiniz?

Bir örneğe bakalım. Olarak temsil edebileceğimiz, uzunluk 1 ve 2'nin 8 çubuğundan oluşan belirli bir zinciri düşünün [1, 1, 2, 2, 1, 1, 2, 2]. Rotasyonlara ve çevirilere kadar, sadece 8 olası poliomino vardır (farklı yansımaları sayıyoruz):

resim açıklamasını buraya girin

Bu ilk çubuk koyu mavi ve daha sonra çokgeni saat yönünün tersine hareket ettiriyoruz.

Dönme hissi yukarıdaki örnekteki sonucu etkilemez. Ancak [3, 1, 1, 1, 2, 1, 1], aşağıdaki 3 polominoyu veren başka bir zinciri düşünelim :

resim açıklamasını buraya girin

Biz anlamına Bildirimi değil o saat yönünde geçişi gerektirecektir, çünkü son Polyomino bir yansıması sayılabilir.

Aynı uzunlukta daha esnek bir zincirimiz [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]olsaydı, aslında toplam 9 olan diğer bazı polyoninolar arasında her iki yansımayı da oluşturabilirdik:

resim açıklamasını buraya girin

Meydan okuma

Bir zincirin, bir dizi veya benzeri olarak bir açıklaması verildiğinde , çevreyi saat yönünün tersine hareket ederken çubukları sırayla kullanarak oluşturabileceğiniz (dönüşlere ve çevirilere kadar) farklı poliomino sayısını belirleyin .

Lütfen tam bir program yazın ve derlemek (varsa) ve komut satırından kodunuzu çalıştırmak için komutlar ekleyin. Lütfen kendi diliniz için ücretsiz bir derleyici / tercüman bağlantısı da ekleyin.

Programınız STDIN girişini okumalıdır. İlk satır bir tamsayı M içerecektir . Bir sonraki M çizgileri, her biri boşlukla ayrılmış bir çubuk uzunluğu listesi olacak test senaryoları olacaktır. Programınız , her biri tek bir tam sayıdan oluşabilecek STDOUT'a M satırları yazdırmalıdır - oluşturulabilen farklı poliomino sayısı.

Sadece tek bir iplik kullanmalısınız.

Programınız hiçbir zaman 1 GB'den fazla bellek kullanmamalıdır. (Bu tamamen katı bir sınır değildir, ancak yürütülebilir dosyalarınızın bellek kullanımını izleyeceğim ve sürekli olarak 1 GB'den fazla kullanan veya önemli ölçüde üzerinde artış olan herhangi bir işlemi öldüreceğim.)

Aşırı miktarda ön hesaplama yapılmasını önlemek için kodunuz 20.000 bayttan uzun olmamalı ve herhangi bir dosya okumamalısınız.

Ayrıca seçilen belirli test senaryolarına göre optimize etmemelisiniz (örneğin, sonuçlarını kodlayarak). Yaptığınızdan şüpheleniyorsam, yeni kıyaslama setleri oluşturma hakkını saklı tutarım. Test setleri rasgele olduğundan, programınızın bunlardaki performansı, rasgele girdi performansı için temsilci olmalıdır. Yapmanıza izin verilen tek varsayım, çubuk uzunluklarının toplamının eşit olmasıdır.

puanlama

N = 10, 11, ..., 20 çubuklu zincirler için karşılaştırma setleri sağladım. Her test seti, uzunlukları 1 ile 4 arasında olan 50 rastgele zincir içerir.

Birincil puanınız, programınızın tüm test setini 5 dakika içinde tamamladığı en büyük N'dir (makinemde, Windows 8 altında). Bağlantı kesici, programınız tarafından bu test setinde geçen gerçek zaman olacaktır.

Birisi en büyük test setini geçerse, daha büyük olanları eklemeye devam edeceğim.

Test Durumları

Uygulamanızın doğruluğunu kontrol etmek için aşağıdaki test senaryolarını kullanabilirsiniz.

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

Burada bunlarla birlikte bir girdi dosyası bulabilirsiniz .

Liderler Sıralaması

   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

"deliksiz" gereksiz görünüyor. Bir bitişik zincir ilk etapta delikli polimomlar üretemez.
Sparr

Çoklu iş parçacığına izin veriliyor mu? Ve iş parçacıkları farklı işlemlerde ise, her biri 1 GB kullanıyor mu? : P
feersum

@Sparr Çevre bir köşeye dokunduğunda olabilir. Örneğin, bkz. Bu sayılmamalıdır.
Martin Ender

@feersum Basit olması için, çoklu iş parçacığına hayır diyeceğim (ve zorluğu düzenleyeceğim).
Martin Ender

1
@PeterKagey Bu yorumu yanlış soruna gönderdiniz mi? Böyle görünüyor gitmiş olmalı bu bir .
Martin Ender

Yanıtlar:


5

C ++ 11

Güncellemeler: cMesafe başlangıç ​​noktasından çok uzaksa , ilk satırın ilkini ekledi (değişkenin tüm amacı buydu rlen, ancak ilk versiyonda yazmayı unuttum). Çok daha az bellek kullanmak için değiştirdim, ama zaman pahasına. Şimdi benim için 5 dakikadan kısa sürede N = 20'yi çözüyor.

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

Şununla derleyin:

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

doze #defines tho
Soham Chowdhury

Başka cevapların yokluğunda ... burada, bir ödülün var!
Sp3000

3

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

İle çalıştırın ./pypy <filename>.


Bu, Martin ile soruyu tartışırken yazdığım referans uygulamasıdır. Hız göz önünde bulundurularak yapılmadı ve oldukça çirkin, ama işleri başlatmak için iyi bir temel sağlamalıdır.

N = 18, mütevazı dizüstü bilgisayarımda yaklaşık 2,5 dakika sürüyor.

Algoritma

Döndürmeler, her şeklin bir dizi Fileri, Asaat yönünün tersine dönüş ve Csaat yönünde dönüş için, şeklin sınırındaki her kafes noktasında bir dizi dönüştürülerek kontrol edilir - Buna bir açı dizesi diyorum . Açı biçimleri döngüsel permütasyonlarsa iki şekil rotasyonel olarak aynıdır. Bunu her zaman iki açı telini doğrudan karşılaştırarak kontrol etmek yerine, yeni bir şekil bulduğumuzda saklamadan önce kanonik bir forma dönüştürürüz. Yeni bir adayımız olduğunda, kanonik forma dönüştürüyoruz ve zaten buna sahip olup olmadığımızı kontrol ediyoruz (böylece tüm set boyunca yinelemek yerine karmadan yararlanmak).

Açı dizisi, As sayısının s sayısını 4 ile aştığından emin olarak şeklin saat yönünün tersine oluşturulup oluşturulmadığını kontrol etmek için de kullanılır C.

Kendiliğinden kesişme, şeklin sınırındaki her kafes noktasını saklayarak ve bir noktanın iki kez ziyaret edilip edilmediğini görerek saf şekilde kontrol edilir.

Çekirdek algoritması basittir, ilk çubuğu sağa yerleştirir, ardından kalan çubuklar için tüm olasılıkları dener. Çubuklar başlangıç ​​noktasından çok uzak bir noktaya ulaşırsa (yani kalan çubuk uzunluklarının toplamı, Manhattan'ın başlangıç ​​noktasından Manhattan mesafesinden daha az), o zaman bu alt ağacı aramayı erken durdururuz.

Güncellemeler (en yeniler önce)

  • 6/12: Kanonik form tanıtıldı, birkaç mikro optimizasyon eklendi
  • 5/12: Algoritma açıklamasında hata düzeltildi. İkinci dereceden döngüsel permütasyon kontrol algoritmasını A, B döngüsel permütasyonları kullanarak B + B yönteminin bir alt dizesi kullanarak doğrusal hale getirdim (bunu neden daha önce yapmadığımı bilmiyorum).
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.