超音速ドミノタイル


10

仕事

三つの整数読み取りプログラム書き込みmはnは STDINから、またはコマンドライン引数のいずれかとして、寸法の矩形のすべての可能なタイル貼りの印刷N×M個により2×1及び1×2ドミノ、最終的に有効なタイリングの数。

個々のタイリングのドミノは、2×1の-場合は2つのダッシュ(|)、1×2ドミノの場合は2つの垂直バー()で表す必要があります。各タイリング(最後のタイリングを含む)の後にはラインフィードが必要です。

スコアリングの目的で、STDINから、またはプログラムが有効なタイルの数のみを印刷し、タイル自体は印刷しないようにするコマンドライン引数としてフラグを受け入れる必要もあります。

プログラムは1024バイト以下にする必要があります。 n≤64のようなすべての入力に対して機能する必要があります。

4x6長方形のすべてのドミノタイルを印刷することに触発されています。)

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

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

得点

スコアは、フラグが設定された入力8 8に対するプログラムの実行時間によって決まります。

これを最速のコンピュータチャレンジではなく最速のコードにするために、すべての提出物を自分のコンピュータ(Intel Core i7-3770、16 GiB PC3-12800 RAM)で実行して公式スコアを決定します。

コードをコンパイルまたは実行する方法の詳細な手順を残してください。言語のコンパイラ/インタプリタの特定のバージョンが必要な場合は、その旨を表明してください。

以下の場合、私は提出物にスコアを付けないままにする権利を留保します。

  • 私のオペレーティングシステム(Fedora 21、64ビット)用の無料の(ビールのような)コンパイラ/インタープリタはありません。

  • 私たちの努力にもかかわらず、あなたのコードは機能しないか、私のコンピューターで正しくない出力を生成します。

  • コンパイルまたは実行には1時間以上かかります。

  • コードまたは利用可能な唯一のコンパイラ/インタープリタには、システムコールrm -rf ~または同様に魚のようなものが含まれています。

リーダーボード

コンパイルと実行の両方を、コンパイルでは10,000回、実行では(コードの速度に応じて)100から10,000回のループで実行し、平均を計算して、すべての送信を再評価しました。

これらは結果でした:

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.

これをゴルフコンテストにしてみませんか?:(
orlp 2015年

2
サンドボックスでそれを提案していたなら、私はそうかもしれない。それは私のCPUと私に多くの仕事を節約させたでしょう...
デニス

3
@ kirbyfan64sos私​​が理解しているように、回転できるドミノのタイプは1つだけです。水平の場合、次のようになります--。垂直の場合は、上下に2 |つあります。
Reto Koradi 2015年

1
あなたの挑戦は悪くありません。問題は、トップコーダーが強すぎるということです。行と列の有効性をチェックする私のソリューションは、6x8で1分近くに留まります。
edc65

1
現時点での最善の戦略は、アセンブリを使用して、複雑な時間を取り除くために、1024バイト未満のバイナリファイルを取得することです。
jimmy23013

回答:


5

C

簡単な実装...

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

浮気バージョン

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

より高速なアルゴリズムの説明

左から右にスキャンし、状態を維持しますd[i][j]

  • iはに[0,m)あり、現在の列を意味します。
  • jsizeのビットベクトルです。この列での作業を開始する前nにcolumn内の対応する位置iが既に占有されている場合、ビットは1になります。つまり、の右半分が占めてい--ます。
  • d[i][j] 異なるタイルの総数です。

次に、e[i][j]= を形成するためd[i][k]に垂直ドミノのベースを配置できる場所の合計を言います。の各1ビットがaの左半分以外で占有されているタイリングの数になります。それらを入力すると、= が得られます。または最終的な答えです。kje[i][j]j----d[i+1][~j]e[i][j]e[m-1][every bit being 1]d[m][0]

素朴な実装では、時間の複雑さがどこかで得られますg[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(n = m = 8の場合はすでに十分高速です)。ただし、代わりに、可能な各ドミノについて最初にループし、このドミノを追加できるすべてのタイリングに追加して、結果を元の配列にマージすることができますd(ナップザック問題のアルゴリズムのように)。そして、これはO(n * 2 ^ n)になります。その他はすべて実装の詳細です。コード全体はO(m * n * 2 ^ n)で実行されます。


@Dennisおそらくそれを変更するために投票を開始したいでしょう。
jimmy23013 2015年

@Dennisサイズを大きくすることで大きな効果があったかどうかはわかりません。計算時間は大幅に増加しますが、出力は約100倍になります。相対的に言えば、出力の量は実際には大きいです。
Reto Koradi

最初のバージョンの実行:0.286秒コンパイル:0.053秒合計:0.339秒2番目のバージョンの実行:0.002秒コンパイル:0.061秒合計:0.063秒(ここで何が起こったのですか?)
デニス

@Dennisフラグが設定されている場合、O(m * n * 2 ^ n)で別のアルゴリズムを使用しました。
jimmy23013

1
実行:190ミリ秒コンパイル:68ミリ秒合計:258ミリ秒(-O1スイートスポットのようです。すべての最適化レベルを試しました。)
Dennis

3

C

一連の最適化の後、変更されたルールに適応します。

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文字の長さ制限にぶつかり始めたので、読みやすさをいくらか低下させる必要がありました。はるかに短い変数名など

ビルド手順:

> gcc -O2 Code.c

ソリューション出力を有効にして実行:

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

ソリューション数のみで実行:

> ./a.out 8 8 s

いくつかのコメント:

  • より大きなテストの例では、今最適化が必要です。私のシステムは異なりますが(Mac)、周り-O2は良いようです。
  • 出力が生成される場合、コードは遅くなります。これは、「カウントのみ」モードを最適化し、コード長を短縮するための意識的な犠牲でした。
  • システム関数のインクルードと外部宣言がないため、コンパイラの警告がいくつか表示されます。これは、コードを完全に読めなくすることなく、最終的に1024文字未満にする最も簡単な方法でした。

また、「カウントのみ」モードでも、コードは実際のソリューションを生成することに注意してください。解が見つかるたびに、vMビットマスクに1は、垂直バーのある0位置のと、水平バーのある位置のが含まれます。このビットマスクのASCII形式への変換と実際の出力のみがスキップされます。


@Dennis新しいバージョン。実行は変更されませんが、コンパイルは速くなります。コンパイル時間を最適化する必要がある場合、システムヘッダーは必要ありません。
Reto Koradi

@Dennis新しいスコアリングおよび一連の最適化のために更新されました。ここで最適化が必要なことに注意してください-O2
Reto Koradi

実行:256ミリ秒コンパイル:65ミリ秒合計:321ミリ秒(-O2スイートスポットのようです。すべての最適化レベルを試しました。)
Dennis

1

C

コンセプトは、最初に水平方向のドミノのすべての可能な配置を見つけて格納し、r[]次にそれらを整理して垂直方向のドミノのすべての可能な配置を提供することです。

行に水平方向のドミノを配置するためのコードは、この私の答え:https : //codegolf.stackexchange.com/a/37888/15599から変更されています。グリッドが広いほど遅くなりますが、8x8の場合は問題ありません。

革新は列が集まっている方法にあります。ボードに奇数の行がある場合、入力解析で90度回転するため、偶数の行があります。次に、中心線を横切るように垂直のドミノをいくつか配置します。対称性のためc、残りのドミノを下半分cに配置する方法がある場合は、上半分に残りのドミノを配置する方法も必要です。つまり、中心線上の垂直ドミノの特定の配置について、c*c可能な解決策があります。 。したがって、プログラムがソリューションの数だけを印刷する必要がある場合は、中心線とボードの半分だけが分析されます。

f()水平ドミノの可能な配置のテーブルを作成し、中心線上の垂直ドミノの可能な配置をスキャンします。次にg()、行を埋める再帰関数を呼び出します。印刷が必要な場合、h()これを行うために関数が呼び出されます。

g()3つのパラメーターで呼び出されます。yは現在の行であり d、ボードを中心から外側に向かって埋める方向(上または下)です。xビットマップが含まれ、前の行から不完全な垂直ドミノを示します。r []からの行のドミノのすべての可能な配置が試行されます。この配列では、1は垂直ドミノを表し、ゼロのペアは水平ドミノを表します。配列の有効なエントリには、最後の行の不完全な垂直ドミノを終了するために少なくとも1が必要です(x&r[j])==x。新しい垂直ドミノが開始されていることを示す1が複数ある可能性があります。次の行では、新しいドミノだけが必要なので、を使用してプロシージャを再度呼び出しますx^r[j]

最後の行に達し、ボードの上部または下部に不完全な垂直ドミノが掛かっていないx^r[j]==0場合、半分は正常に完了しています。印刷しない場合は、下半分を完成させてc*c、アレンジメントの総数を計算するのに十分です。印刷する場合は、上半分も完成させてから、印刷機能を呼び出す必要がありますh()

コード

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

}

奇数の行と偶数の列を持つ入力は、解析フェーズで90度回転します。これが受け入れられない場合は、印刷機能h()を変更して対応できます。(編集:必須ではありません。コメントを参照してください。)

編集:(センターラインにまたがるドミノの数)のパリティe()をチェックするために新しい関数が使用されましたii(ボードの各半分に突き出ているセンターライン上のハーフドミノの数)のパリティは、n/2ドミノが利用可能なすべてのスペースを埋めることができるのはその時だけなので、各半分のスペースの総数の奇数(で与えられる)。この編集により、iの値の半分が削除されるため、プログラムの速度が約2倍になります。


実行:18ミリ秒コンパイル:50ミリ秒合計:68ミリ秒(-O0合計のスイートスポットでした。他のオプションはコンパイルを遅くしました。)
Dennis

inputの場合、これは決して終了しないか、少なくとも非常に長い時間がかかります32 2 s。15分くらいで止まりました。
Reto Koradi

@RetoKoradiは確かに、2 32 sほとんど瞬時に実行されます。可能性のあるすべての垂直ドミノをスキャンするH=2ことは、実際には必要なすべての情報をすでに持っているため、このケースでは非常に無駄ですr[]。私は非常にために公式の時間に満足しています8 8 sここでは言及ケースのためのパッチです: 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;あなたはこのスニペットはのためにすぐに実行されます見ることができるようにH=2 フラグが設定されています。その場合、全体的な実行時間は、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);期すために、必要に応じて出力を正しい方向に向けるためのパッチを以下に示します。Codelengthは依然として1000バイトを十分に下回っており、コンパイル時間への影響は最小限である必要があります。疲れすぎて昨夜これらのパッチを含めなかった。
Level River St

昨夜そのことについてコメントするつもりでしたが、忘れました。スコアリングは正方形で行われるので、特定の順序を主張するつもりはありません。
Dennis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.