Membentuk Polyominoes dengan Chain of Rods

20

Latar Belakang

Pertimbangkan rantai batang (tertutup), yang masing-masing memiliki panjang bilangan bulat. Berapa banyak polyomino bebas lubang yang bisa Anda bentuk dengan rantai tertentu? Atau dengan kata lain, berapa banyak poligon yang tidak berpotongan sendiri yang berbeda dengan sisi yang selaras sumbu yang dapat Anda bentuk dengan rantai tertentu?

Mari kita lihat sebuah contoh. Pertimbangkan rantai tertentu yang terdiri dari 8 batang dengan panjang 1 dan 2, yang dapat kita wakili [1, 1, 2, 2, 1, 1, 2, 2]. Hingga rotasi dan terjemahan, hanya ada 8 kemungkinan polyomino (kami menghitung refleksi yang berbeda):

masukkan deskripsi gambar di sini

Batang pertama ini berwarna biru tua, dan kemudian kita melintasi poligon dalam arti berlawanan arah jarum jam .

Perasaan rotasi tidak mempengaruhi hasil dalam contoh di atas. Tapi mari kita pertimbangkan rantai lain [3, 1, 1, 1, 2, 1, 1], yang menghasilkan 3 polyomino berikut:

masukkan deskripsi gambar di sini

Perhatikan kami melakukannya tidak menyertakan refleksi dari polyomino terakhir, karena itu akan memerlukan traverse searah jarum jam.

Jika kita memiliki rantai yang lebih fleksibel dengan panjang yang sama [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], kita sebenarnya dapat membentuk kedua refleksi di antara beberapa polyonino lainnya, dengan total 9:

masukkan deskripsi gambar di sini

Tantangan

Diberikan deskripsi rantai, sebagai array atau sejenisnya, tentukan jumlah polyomino berbeda yang dapat Anda bentuk (hingga rotasi dan terjemahan) dengan menggunakan batang sesuai urutan sambil berkeliling di sekeliling perimeter dalam arti berlawanan arah jarum jam.

Silakan tulis program lengkap dan sertakan perintah untuk dikompilasi (jika ada) dan jalankan kode Anda dari baris perintah. Harap sertakan juga tautan ke kompiler / juru bahasa gratis untuk bahasa Anda.

Program Anda harus membaca input dari STDIN. Baris pertama akan berisi integer M . Baris M berikutnya akan menjadi kasus uji, yang masing-masing akan menjadi daftar panjang batang yang dipisahkan ruang. Program Anda harus mencetak garis M ke STDOUT, yang masing-masing terdiri dari bilangan bulat tunggal - jumlah poliomino berbeda yang dapat dibentuk.

Anda hanya harus menggunakan utas tunggal.

Program Anda tidak boleh menggunakan lebih dari 1 GB memori kapan saja. (Ini bukan batas yang sepenuhnya ketat, tapi saya akan memantau penggunaan memori Anda yang dapat dieksekusi, dan membunuh proses apa pun yang secara konsisten menggunakan lebih dari 1 GB, atau lonjakan secara signifikan di atasnya.)

Untuk mencegah jumlah pra-komputasi yang berlebihan, kode Anda tidak boleh lebih dari 20.000 byte, dan Anda tidak boleh membaca file apa pun.

Anda juga harus tidak mengoptimalkan terhadap kasus uji tertentu yang dipilih (misalnya dengan meng-hardcoding hasil mereka). Jika saya menduga Anda melakukannya, saya berhak untuk membuat set benchmark baru. Set tes acak, sehingga kinerja program Anda pada mereka harus representatif untuk kinerjanya pada input sewenang-wenang. Satu-satunya asumsi yang Anda boleh buat adalah bahwa jumlah panjang batang adalah genap.

Mencetak gol

Saya telah menyediakan set tolok ukur untuk rantai N = 10, 11, ..., 20 batang. Setiap set tes berisi 50 rantai acak dengan panjang antara 1 dan 4 inklusif.

Skor utama Anda adalah N terbesar di mana program Anda menyelesaikan seluruh rangkaian tes dalam waktu 5 menit (pada mesin saya, di bawah Windows 8). Tie breaker adalah waktu yang sebenarnya diambil oleh program Anda pada set tes tersebut.

Jika ada yang mengalahkan set tes terbesar, saya akan terus menambahkan yang lebih besar.

Uji Kasus

Anda dapat menggunakan test case berikut untuk memeriksa kebenaran implementasi Anda.

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

Anda menemukan file input dengan ini di sini .

Papan peringkat

   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
sumber
"bebas lubang" tampaknya berlebihan. satu rantai yang berdekatan tidak dapat menghasilkan polyomino berlubang di tempat pertama.
Sparr
Apakah multi-threading diizinkan? Dan jika utas berada dalam proses yang berbeda, apakah masing-masing dapat menggunakan 1 GB? : P
feersum
@Parr Itu bisa ketika perimeter menyentuh dirinya sendiri di sudut. Misalnya, lihat No. 81 di sini. Yang itu seharusnya tidak dihitung.
Martin Ender
@feersum Untuk mempermudah, saya akan mengatakan tidak untuk multi-threading (dan akan mengedit tantangan).
Martin Ender
1
@PeterKagey Apakah Anda memposting komentar ini pada tantangan yang salah? Terlihat seperti itu harus pergi pada satu ini .
Martin Ender

Jawaban:

5

C ++ 11

Pembaruan: Menambahkan baris pertama cyang pecah lebih awal jika jaraknya terlalu jauh dari titik asal (yang merupakan seluruh tujuan variabel rlen, tetapi saya lupa menuliskannya dalam versi pertama). Saya mengubahnya untuk menggunakan memori jauh lebih sedikit, tetapi dengan biaya waktu. Sekarang memecahkan N = 20 hanya di bawah 5 menit untuk saya.

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

Kompilasi dengan

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe
feersum
sumber
doze #defines tho
Soham Chowdhury
Dengan tidak adanya jawaban lain ... di sini, dapatkan karunia!
Sp3000
3

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

Jalankan dengan ./pypy <filename>.


Ini adalah implementasi referensi yang saya tulis ketika membahas pertanyaan dengan Martin. Itu tidak dibuat dengan kecepatan dalam pikiran dan cukup hacky, tetapi harus memberikan dasar yang baik untuk memulai sesuatu.

N = 18 membutuhkan sekitar 2,5 menit pada laptop sederhana saya.

Algoritma

Rotasi diperiksa dengan mengubah setiap bentuk menjadi serangkaian Funtuk maju, Auntuk berbelok berlawanan arah jarum jam dan Cuntuk mengubah arah jarum jam pada setiap titik kisi pada batas bentuk - Saya menyebutnya string sudut . Dua bentuk identik secara rotasi jika string sudutnya adalah permutasi siklik. Daripada selalu memeriksa ini dengan membandingkan dua string sudut secara langsung, ketika kami menemukan bentuk baru, kami mengonversi ke bentuk kanonik sebelum menyimpan. Ketika kita memiliki kandidat baru, kita beralih ke bentuk kanonik dan memeriksa apakah kita sudah memiliki ini (sehingga mengeksploitasi hashing, daripada iterasi melalui seluruh set).

String sudut juga digunakan untuk memeriksa bahwa bentuknya dibentuk berlawanan arah jarum jam, dengan memastikan bahwa jumlah As melebihi jumlahC s dengan 4.

Simpang-sendiri diperiksa secara naif dengan menyimpan setiap titik kisi pada batas bentuk, dan melihat apakah suatu titik dikunjungi dua kali.

Algoritma inti sederhana, menempatkan batang pertama ke kanan, lalu mencoba semua kemungkinan untuk batang yang tersisa. Jika batang mencapai titik yang terlalu jauh dari titik asal (yaitu jumlah panjang batang yang tersisa kurang dari jarak titik Manhattan dari titik asal), maka kami sebelum waktunya berhenti mencari subtree itu.

Pembaruan (terbaru lebih dulu)

  • 6/12: Bentuk kanonik yang diperkenalkan, ditambahkan beberapa optimisasi mikro
  • 5/12: Memperbaiki kesalahan dalam penjelasan algoritma. Membuat kuadratik siklik-permutasi memeriksa algoritma linear menggunakan permutasi siklik A, B iff A substring dari metode B + B (Saya tidak tahu mengapa saya tidak melakukan ini sebelumnya).
Sp3000
sumber