Ubin domino supersonik

10

Tugas

Tulis sebuah program yang bertuliskan tiga bilangan bulat m , n baik dari STDIN atau sebagai argumen baris perintah, mencetak semua kemiringan yang mungkin dari segi empat dimensi m × n dengan domino 2 × 1 dan 1 × 2 dan akhirnya jumlah tilings yang valid.

Domino dari ubin individu harus diwakili oleh dua garis ( -) untuk 2 × 1 dan dua batang vertikal ( |) untuk 1 × 2 kartu domino . Setiap ubin (termasuk yang terakhir) harus diikuti oleh umpan baris.

Untuk tujuan penilaian, Anda juga harus menerima bendera dari STDIN atau sebagai argumen baris perintah yang membuat program Anda hanya mencetak jumlah tilings yang valid, tetapi bukan tilings itu sendiri.

Program Anda mungkin tidak lebih dari 1024 byte. Ini harus bekerja untuk semua input sehingga m × n ≤ 64 .

(Terinspirasi oleh Cetak semua domino miring dari persegi panjang 4x6 .)

Contoh

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

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

Mencetak gol

Skor Anda ditentukan oleh waktu pelaksanaan program Anda untuk input 8 8 dengan flag yang ditetapkan.

Untuk menjadikan ini kode tercepat daripada tantangan komputer tercepat , saya akan menjalankan semua pengiriman di komputer saya sendiri (Intel Core i7-3770, 16 GiB PC3-12800 RAM) untuk menentukan skor resmi.

Silakan tinggalkan instruksi rinci tentang cara menyusun dan / atau menjalankan kode Anda. Jika Anda memerlukan versi spesifik dari kompiler / juru bahasa Anda, buat pernyataan tentang hal itu.

Saya berhak untuk meninggalkan kiriman tanpa catatan jika:

  • Tidak ada kompiler / juru bahasa gratis (seperti dalam bir) untuk sistem operasi saya (Fedora 21, 64 bit).

  • Terlepas dari upaya kami, kode Anda tidak berfungsi dan / atau menghasilkan output yang salah di komputer saya.

  • Kompilasi atau eksekusi memakan waktu lebih dari satu jam.

  • Kode Anda atau satu-satunya kompiler / juru bahasa yang tersedia berisi panggilan sistem ke rm -rf ~atau sesuatu yang sama-sama mencurigakan.

Papan peringkat

Saya telah mencetak ulang semua kiriman, menjalankan kompilasi dan eksekusi dalam satu lingkaran dengan 10.000 iterasi untuk kompilasi dan antara 100 dan 10.000 iterasi untuk eksekusi (tergantung pada kecepatan kode) dan menghitung rata-rata.

Inilah hasilnya:

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
sumber
Mengapa tidak menjadikan ini kontes GOLF? :(
orlp
2
Jika Anda menyarankan itu di kotak pasir, saya mungkin punya. Itu akan menghemat CPU saya dan saya banyak pekerjaan ...
Dennis
3
@ kirbyfan64sos Cara saya memahaminya, hanya ada satu jenis domino, yang dapat Anda putar. Jika horisontal, terlihat seperti ini: --. Jika vertikal, itu dua |, satu di bawah yang lain.
Reto Koradi
1
Tantangan Anda tidak buruk. Masalahnya adalah coders top kami terlalu kuat. Solusi saya yang memeriksa validitas baris dan kolom tetap dekat 1 menit selama 6x8.
edc65
1
Saya pikir strategi terbaik sekarang adalah menggunakan assembly, dan mencoba untuk mendapatkan file biner kurang dari 1024 byte, untuk menghilangkan waktu komplikasi.
jimmy23013

Jawaban:

5

C

Implementasi yang mudah ...

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

Versi curang

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

Penjelasan algoritma yang lebih cepat

Memindai dari kiri ke kanan, dan mempertahankan status d[i][j], di mana:

  • idalam [0,m), yang berarti kolom saat ini.
  • jadalah bit vektor ukuran n, di mana bit akan menjadi 1 jika posisi yang sesuai dalam kolom isudah ditempati sebelum mulai bekerja pada kolom ini. Yaitu ditempati oleh setengah kanan a --.
  • d[i][j] adalah jumlah total tilings yang berbeda.

Lalu ucapkan e[i][j]= jumlah d[i][k]tempat Anda dapat meletakkan dasar kartu domino vertikal kuntuk membentuk a j. e[i][j]akan menjadi jumlah miring di mana setiap 1 bit jditempati oleh apa pun kecuali setengah dari a --. Isi dengan --dan Anda akan mendapatkan d[i+1][~j]= e[i][j]. e[m-1][every bit being 1]atau d[m][0]jawaban terakhir.

Implementasi naif akan memberi Anda kompleksitas waktu di suatu tempat dekat g[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(sudah cukup cepat jika n = m = 8). Tetapi Anda dapat terlebih dahulu mengulang untuk setiap domino yang mungkin, dan mencoba menambahkannya ke setiap ubin yang dapat ditambahkan domino ini, dan menggabungkan hasilnya ke array asli d(seperti algoritma untuk masalah Knapsack). Dan ini menjadi O (n * 2 ^ n). Dan yang lainnya adalah detail implementasi. Seluruh kode berjalan dalam O (m * n * 2 ^ n).

jimmy23013
sumber
@ Dennis Anda mungkin ingin memulai polling untuk mengubahnya.
jimmy23013
@ Dennis Tidak yakin meningkatkan ukuran akan banyak membantu. Sementara itu meningkatkan waktu komputasi secara substansial, itu juga menghasilkan sekitar 100 kali lebih banyak output. Secara relatif, jumlah output sebenarnya lebih besar.
Reto Koradi
Eksekusi versi 1 : Kompilasi 0,286 s: 0,053 s Jumlah: 0,339 s Versi ke-2 Eksekusi: 0,002 s Kompilasi: 0,061 s Jumlah: 0,063 s (Apa yang terjadi di sini?)
Dennis
@ Dennis Ini menggunakan algoritma lain dalam O (m * n * 2 ^ n) jika flag diatur.
jimmy23013
1
Eksekusi: Kompilasi 190 ms: 68 ms Jumlah: 258 ms ( -O1tampaknya menjadi titik manis. Saya sudah mencoba semua level optimisasi.)
Dennis
3

C

Setelah putaran optimasi, dan diadaptasi untuk aturan yang dimodifikasi:

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

Saya mulai menabrak batas panjang 1024 karakter, jadi saya harus mengurangi keterbacaan. Nama variabel jauh lebih pendek, dll.

Membangun instruksi:

> gcc -O2 Code.c

Jalankan dengan solusi yang diaktifkan:

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

Jalankan dengan hanya jumlah solusi:

> ./a.out 8 8 s

Beberapa komentar:

  • Dengan contoh pengujian yang lebih besar, saya ingin optimasi sekarang. Meskipun sistem saya berbeda (Mac), -O2kelihatannya bagus.
  • Kode menjadi lebih lambat untuk kasus di mana output dihasilkan. Ini adalah pengorbanan sadar untuk mengoptimalkan mode "hitung saja", dan untuk mengurangi panjang kode.
  • Akan ada beberapa peringatan kompiler karena tidak menyertakan dan deklarasi eksternal untuk fungsi sistem. Itu adalah cara termudah untuk membuat saya di bawah 1024 karakter pada akhirnya tanpa membuat kode sama sekali tidak dapat dibaca.

Perhatikan juga bahwa kode masih menghasilkan solusi aktual, bahkan dalam mode "hitung saja". Setiap kali solusi ditemukan, vMbitmask berisi a 1untuk posisi yang memiliki bilah vertikal, dan a 0untuk posisi dengan bilah horizontal. Hanya konversi bitmask ini ke dalam format ASCII, dan output aktual, dilewati.

Reto Koradi
sumber
@ Dennis Versi baru. Eksekusi harus tidak berubah, tetapi kompilasi lebih cepat. Jika kita perlu mengoptimalkan waktu kompilasi, kita tidak perlu header sistem!
Reto Koradi
@Dennis Diperbarui untuk penilaian baru, dan untuk serangkaian optimisasi. Perhatikan bahwa saya ingin pengoptimalan sekarang, sesuatu seperti -O2harus baik.
Reto Koradi
Eksekusi: 256 ms Kompilasi: 65 ms Jumlah: 321 ms ( -O2tampaknya menjadi sweet spot. Saya sudah mencoba semua level optimasi.)
Dennis
1

C

Konsepnya adalah untuk pertama-tama menemukan semua kemungkinan pengaturan domino horizontal dalam satu baris, menyimpannya r[]dan kemudian mengaturnya untuk memberikan semua kemungkinan pengaturan domino vertikal.

Kode untuk memposisikan domino horisontal secara berturut-turut dimodifikasi dari jawaban saya: https://codegolf.stackexchange.com/a/37888/15599 . Ini lambat untuk grid yang lebih luas tapi itu bukan masalah untuk kasus 8x8.

Inovasi adalah cara baris dirakit. Jika papan memiliki jumlah baris ganjil, papan tersebut diputar hingga 90 derajat pada parsing input, sehingga sekarang memiliki jumlah baris genap. Sekarang saya menempatkan beberapa kartu domino vertikal di garis tengah. Karena simetri, jika ada ccara untuk mengatur domino yang tersisa di bagian bawah, juga harus ada ccara mengatur domino yang tersisa di bagian atas, yang berarti bahwa untuk pengaturan domino domino vertikal pada garis tengah, ada c*csolusi yang memungkinkan. . Oleh karena itu, hanya garis tengah ditambah setengah papan yang dianalisis ketika program diharuskan untuk mencetak hanya sejumlah solusi.

f()membangun tabel kemungkinan pengaturan domino horizontal, dan memindai melalui kemungkinan pengaturan domino vertikal di garis tengah. kemudian memanggil fungsi rekursif g()yang mengisi baris. Jika diperlukan pencetakan, fungsi h()dipanggil untuk melakukan ini.

g()disebut dengan 3 parameter. yadalah baris saat ini dan dmerupakan arah (naik atau turun) di mana kita mengisi papan dari tengah ke luar. xberisi bitmap menunjukkan domino vertikal yang tidak lengkap dari baris sebelumnya. Semua pengaturan domino yang mungkin secara berurutan dari r [] dicoba. Dalam array ini, 1 mewakili domino vertikal dan sepasang nol merupakan domino horizontal. Sebuah entri yang valid dalam array harus memiliki setidaknya cukup 1 untuk menyelesaikan setiap domino vertikal yang tidak lengkap dari baris terakhir: (x&r[j])==x. Ini mungkin memiliki lebih dari 1 yang menunjukkan bahwa domino vertikal baru sedang dimulai. Untuk baris berikutnya, maka, kita hanya perlu domino baru sehingga kita memanggil prosedurnya lagi x^r[j].

Jika baris akhir telah tercapai dan tidak ada domino vertikal tidak lengkap yang tergantung di bagian atas atau bawah papan, x^r[j]==0maka separuh telah berhasil diselesaikan. Jika kita tidak mencetak, itu cukup untuk menyelesaikan setengah bagian bawah dan digunakan c*cuntuk menghitung jumlah total pengaturan. Jika kita mencetak, perlu juga untuk menyelesaikan setengah bagian atas dan kemudian memanggil fungsi pencetakan h().

KODE

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

}

Perhatikan bahwa input dengan jumlah baris dan jumlah genap ganjil diputar 90 derajat pada fase parsing. Jika ini tidak dapat diterima, fungsi pencetakan h()dapat diubah untuk mengakomodasi itu. (EDIT: tidak wajib, lihat komentar.)

EDIT: Fungsi baru e()telah digunakan untuk memeriksa paritas i(yaitu jumlah domino yang mengangkang garis tengah). Paritas i(jumlah setengah domino pada garis tengah yang menjorok ke setiap setengah papan) harus sama dengan keanehan dari jumlah total ruang di setiap setengah (diberikan oleh n/2) karena hanya dengan demikian domino dapat mengisi semua ruang yang tersedia. Hasil edit ini menghilangkan setengah nilai i dan karenanya membuat program saya kira-kira dua kali lebih cepat.

Level River St
sumber
Eksekusi: Kompilasi 18 ms: 50 ms Jumlah: 68 ms ( -O0adalah sweet spot untuk total. Opsi lain memperlambat kompilasi.)
Dennis
Entah ini tidak pernah berakhir, atau setidaknya membutuhkan waktu yang sangat lama, untuk input 32 2 s. Saya menghentikannya setelah sekitar 15 menit.
Reto Koradi
@RetoKoradi memang, belum 2 32 sberjalan hampir seketika. Pemindaian melalui semua kemungkinan domino vertikal sangat boros untuk H=2kasus ini, karena sebenarnya kita sudah memiliki semua informasi yang diperlukan di r[]. Saya sangat senang dengan waktu resmi untuk 8 8 sBerikut adalah tambalan untuk kasus yang Anda sebutkan: 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;Seperti yang Anda lihat, cuplikan ini akan berjalan langsung H=2 dengan set bendera. Keseluruhan runtime kemudian dibatasi oleh bangunan r[]yang tentunya memiliki ruang untuk perbaikan.
Level River St
Untuk kelengkapan, inilah tambalan untuk memutar output dengan cara yang benar, jika diperlukan: 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);Panjang kode masih jauh di bawah 1000 byte dan dampak pada waktu kompilasi harus minimal. Saya tidak memasukkan patch ini tadi malam karena saya terlalu lelah.
Level River St
Saya bermaksud mengomentari tadi malam, tapi saya lupa. Karena penilaian dilakukan pada kotak, saya tidak akan memaksakan urutan tertentu.
Dennis