Süpersonik domino devrimleri


10

Görev

Üç tamsayı okuyan bir program yazın m , n , komut satırı bağımsız değişkenleri STDIN'den ya kendileri ya da boyutlarda bir dikdörtgenin tüm olası tilings basar m x n ile 2 x 1 ve 1 × 2 geçerli tilings sayısına domino ve son olarak.

Tek bir döşemenin dominosları, 2 × 1- için iki tire ( ) ve 1 × 2 dominos için iki dikey çubuk ( ) ile temsil edilmelidir . Her döşemeyi (sonuncusu dahil) bir satır besleme takip etmelidir.|

Puanlama amacıyla, STDIN'den veya programınızın yalnızca geçerli yatırmaların sayısını değil, aynı zamanda yatırmaların kendisini yazdırmasını sağlayan bir komut satırı argümanı olarak kabul etmeniz gerekir.

Programınız 1024 bayttan uzun olamaz. M × n ≤ 64 olacak şekilde tüm girişler için çalışması gerekir .

(Esinlenerek 4x6 dikdörtgenin tüm domino eğimlerini yazdırın .)

Misal

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

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

puanlama

Skorunuz, bayrak ayarlı giriş 8 8 için programınızın yürütme süresine göre belirlenir.

Bunu en hızlı bilgisayar yarışmasından ziyade en hızlı kod yapmak için, resmi puanı belirlemek için tüm başvuruları kendi bilgisayarımda (Intel Core i7-3770, 16 GiB PC3-12800 RAM) çalıştıracağım.

Lütfen kodunuzu nasıl derleyeceğiniz ve / veya nasıl çalıştıracağınız hakkında ayrıntılı talimatlar bırakın. Dilinizin derleyicisinin / yorumlayıcısının belirli bir sürümüne ihtiyacınız varsa, bu konuda bir açıklama yapın.

Aşağıdaki durumlarda gönderimleri kaydedilmemiş olarak bırakma hakkını saklı tutarım:

  • İşletim sistemim için ücretsiz (biradaki gibi) derleyici / yorumlayıcı yok (Fedora 21, 64 bit).

  • Çabalarımıza rağmen, kodunuz çalışmıyor ve / veya bilgisayarımda yanlış çıktı üretiyor.

  • Derleme veya yürütme bir saatten uzun sürer.

  • Kodunuzda veya kullanılabilen tek derleyici / yorumlayıcıda sistem çağrısı rm -rf ~veya eşit derecede balık gibi bir şey bulunur.

Liderler Sıralaması

Derleme için 10.000 yineleme ve yürütme için 100 ila 10.000 yineleme (kodun hızına bağlı olarak) ve ortalama hesaplanması ile bir döngüde hem derlemeler hem de yürütme çalışan tüm başvuruları yeniden puan.

Sonuçlar şunlardı:

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.

Neden bir GOLF yarışması yapmıyorsunuz? :(
orlp

2
Eğer sandbox'ta bunu önermiş olsaydın, yapabilirdim. Bu, CPU'mu ve beni çok fazla işten kurtaracaktı ...
Dennis

3
@ kirbyfan64sos Anlama şeklimde, yalnızca bir tür domino vardır, bu şekilde döndürebilirsiniz. Yatay varsa, bu şuna benzer: --. Dikey ise |, biri diğerinin altında iki .
Reto Koradi

1
Meydan okumanız kötü değil. Sorun şu ki üst kodlayıcılarımız çok güçlü. Satır ve sütunların geçerliliğini kontrol eden çözümüm 6x8 için 1 dakika civarında kalıyor.
edc65

1
Şimdi en iyi stratejinin montajı kullanmak olduğunu ve komplikasyon zamanından kurtulmak için 1024 bayttan daha az bir ikili dosya almaya çalışmak olduğunu düşünüyorum.
jimmy23013

Yanıtlar:


5

C

Basit bir uygulama ...

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

Hile versiyonu

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

Daha hızlı algoritmanın açıklaması

Soldan sağa tarar ve durumu korur d[i][j], burada:

  • i, [0,m)geçerli sütun anlamına gelir.
  • jbir boyut vektörüdür n, burada ibu sütunda çalışmaya başlamadan önce sütundaki karşılık gelen konum zaten dolu ise bit 1 olacaktır . Yani a'nın sağ yarısı tarafından işgal edilir --.
  • d[i][j] toplam farklı eğim sayısıdır.

Ardından demek e[i][j]= toplamı d[i][k]üzerinde dikey domino tabanını koyabilirsiniz nereye kbir oluşturacak şekilde j. e[i][j]her bir 1 bitin a'nın jsol yarısından başka bir şey tarafından işgal edildiği kat sayısıdır --. Onları doldurun --ve d[i+1][~j]= elde edersiniz e[i][j]. e[m-1][every bit being 1]veya d[m][0]son cevaptır.

Saf bir uygulama size yakın bir yerde zaman karmaşıklığı sağlayacaktır g[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(n = m = 8 ise zaten yeterince hızlı). Ancak ilk önce olası her domino için döngü yapabilir ve bu domino'yu ekleyebilecek her döşemeye eklemeyi deneyebilir ve sonucu orijinal diziyle birleştirebilirsiniz d(Knapsack sorununun algoritması gibi). Ve bu O (n * 2 ^ n) olur. Ve diğer her şey uygulama detaylarıdır. Kodun tamamı O (m * n * 2 ^ n) içinde çalışır.


@Dennis Muhtemelen değiştirmek için bir anket başlatmak istiyorsunuz.
jimmy23013

@Dennis Boyutu artırmanın çok yardımcı olacağından emin değilim. Hesaplama süresini önemli ölçüde artırırken, yaklaşık 100 kat daha fazla çıktı üretir. Göreceli olarak, çıktı miktarı aslında daha fazladır.
Reto Koradi

1. versiyon Yürütme: 0.286 s Derleme: 0.053 s Toplam: 0.339 s 2. sürüm Yürütme: 0.002 s Derleme: 0.061 s Toplam: 0.063 s (Burada ne oldu?)
Dennis

@Dennis Bayrak ayarlanmışsa O (m * n * 2 ^ n) içinde başka bir algoritma kullanmıştır.
jimmy23013

1
Yürütme: 190 ms Derleme: 68 ms Toplam: 258 ms ( -O1en tatlı nokta gibi görünüyor. Tüm optimizasyon düzeylerini denedim.)
Dennis

3

C

Bir dizi optimizasyondan sonra ve değiştirilen kurallara uyarlandı:

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

1024 karakterlik uzunluk sınırına çarpmaya başladım, bu yüzden okunabilirliği biraz azaltmak zorunda kaldım. Çok daha kısa değişken adları vb.

Derleme talimatları:

> gcc -O2 Code.c

Çözüm çıkışı etkinken çalıştır:

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

Yalnızca çözüm sayısı ile çalıştırın:

> ./a.out 8 8 s

Bazı yorumlar:

  • Daha büyük test örneğiyle, şimdi optimizasyon istiyorum. Sistemim farklı olsa da (Mac) etrafta -O2iyi görünüyor.
  • Kod, çıktının üretildiği durumda yavaşlar. Bu "sadece say" modunu optimize etmek ve kod uzunluğunu azaltmak için bilinçli bir fedakarlıktı.
  • Eksik işlevler ve sistem işlevleri için harici bildirimler nedeniyle birkaç derleyici uyarısı olacaktır. Sonunda kodu tamamen okunamaz hale getirmeden beni 1024 karakterin altına indirmenin en kolay yoluydu.

Ayrıca, kodun "yalnızca say" modunda bile gerçek çözümleri oluşturduğunu unutmayın. Bir çözüm bulunduğunda, vMbit maskesi 1dikey çubuk 0içeren konumlar için a ve yatay çubuklu konumlar için a içerir. Yalnızca bu bitmaskın ASCII formatına dönüştürülmesi ve gerçek çıktı atlanır.


@Dennis Yeni sürüm. Uygulama değiştirilmemeli, ancak derleme daha hızlı olmalıdır. Derleme süresi için optimizasyon yapmamız gerekiyorsa, sistem üstbilgilerine gerek yoktur!
Reto Koradi

@Dennis Yeni puanlama ve bir dizi optimizasyon için güncellendi. Şimdi optimizasyon istediğimi, böyle bir şeyin -O2iyi olması gerektiğini unutmayın.
Reto Koradi

Yürütme: 256 ms Derleme: 65 ms Toplam: 321 ms ( -O2en tatlı nokta gibi görünüyor. Tüm optimizasyon düzeylerini denedim.)
Dennis

1

C

Kavram, ilk önce yatay dominoların tüm olası düzenlemelerini arka arkaya bulmak, saklamak r[]ve daha sonra olası tüm dikey domino düzenlemelerini vermek için organize etmektir.

Yatay domino'ları arka arkaya konumlandırma kodu, bu cevabımdan değiştirildi: https://codegolf.stackexchange.com/a/37888/15599 . Daha geniş ızgaralar için yavaş ama bu 8x8 kasası için sorun değil.

Yenilik, sıraların bir araya getirilme şeklidir. Kartta tek sayıda satır varsa, giriş ayrıştırma işleminde 90 derece döndürülür, bu nedenle şimdi çift sayıda satır vardır. Şimdi merkez hattına dikey dominolar yerleştiriyorum. Simetri nedeniyle c, alt yarıda kalan dominoları düzenlemenin yolları varsa c, üst yarıda kalan dominoları düzenlemenin yolları da olmalıdır , yani merkez çizgisinde belirli bir dikey domino düzenlemesi için c*colası çözümler vardır. . Bu nedenle, programın yalnızca çözüm sayısını yazdırması gerektiğinde, yalnızca merkez çizgisi artı kartın yarısı analiz edilir.

f()yatay dominoların olası düzenlemeleri tablosunu oluşturur ve merkez çizgisindeki dikey dominoların olası düzenlemeleri yoluyla tarar. daha sonra g()satırları dolduran özyinelemeli işlevi çağırır . Yazdırma gerekiyorsa, h()bunu yapmak için işlev çağrılır.

g()3 parametre ile çağrılır. ygeçerli satırdır ve dkartı merkezden dışa doğru doldurduğumuz yön (yukarı veya aşağı). xbir bitmap, bir önceki satırdan eksik olan dikey dominoları gösterir. R [] 'den üst üste tüm olası domino düzenlemeleri denenir. Bu dizide, 1 dikey bir dominoyu ve bir çift sıfır yatay bir dominoyu temsil eder. Dizideki Geçerli bir giriş son satırdan herhangi tamamlanmamış dikey domino bitirmek için en az 1 yeter olması gerekir: (x&r[j])==x. Yeni dikey dominoların başlatıldığını gösteren daha fazla 1 olabilir. Bir sonraki satır için, sadece yeni dominolara ihtiyacımız var, bu yüzden prosedürü tekrar çağırıyoruz x^r[j].

Bir uç satıra ulaşılmışsa ve tahtanın üst veya alt kısmında asılı olmayan dikey dikey domino yoksa x^r[j]==0, yarım başarıyla tamamlanmıştır. Baskı yapmıyorsak, alt yarıyı tamamlamak c*cve toplam düzenleme sayısını hesaplamak için kullanmak yeterlidir . Baskı yapıyorsak, üst yarıyı da tamamlamak ve ardından yazdırma işlevini çağırmak gerekecektir h().

KOD

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

}

Tek bir satır ve çift sütun içeren girdinin ayrıştırma aşamasında 90 derece döndürüldüğünü unutmayın. Bu kabul edilemez durumdaysa, yazdırma işlevi h()ona uygun şekilde değiştirilebilir. (DÜZENLEME: zorunlu değil, yorumlara bakın.)

DÜZENLEME: Yeni bir işlev e()kullanılmıştır i(yani merkez çizgisi iüzerinde bulunan domino sayısı). her yarımdaki toplam boşluk sayısının tuhaflığı (tarafından verilen n/2) çünkü sadece o zaman dominolar mevcut tüm alanı doldurabilir. Bu düzenleme i değerlerinin yarısını ortadan kaldırır ve bu nedenle programımı yaklaşık iki kat daha hızlı yapar.


Yürütme: 18 ms Derleme: 50 ms Toplam: 68 ms ( -O0toplam için en tatlı yerdi. Diğer seçenekler derlemeyi yavaşlattı.)
Dennis

Bu, giriş için ya asla sonlanmaz ya da en azından çok uzun zaman alır 32 2 s. 15 dakika sonra durdurdum.
Reto Koradi

@RetoKoradi gerçekten de, 2 32 sneredeyse anında çalışır. Olası tüm dikey dominoları taramak, H=2dava için son derece israftır , çünkü aslında gerekli tüm bilgilere sahibiz r[]. Resmi zamandan çok memnun kaldım 8 8 s: Bahsettiğiniz dava için bir yama var: 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;Gördüğünüz gibi bu snippet H=2 , bayrak seti ile anında çalışacaktır . Genel çalışma süresi daha sonra r[]kesinlikle iyileştirilmesi gereken bina ile sınırlıdır .
Level River St

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);Tamlık için gerekirse çıktıyı doğru şekilde yükseltmek için yama: Kod uzunluğu hala 1000 baytın çok altında ve derleme süresi üzerindeki etki minimum olmalıdır. Çok yorgun olduğum için dün gece bu yamaları dahil etmedim.
Level River St

Dün gece bu konu hakkında yorum yapmak istedim ama unuttum. Puanlama bir meydanda yapıldığından, belirli bir düzende ısrar etmeyeceğim.
Dennis
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.