Nghiêng domino siêu âm


10

Bài tập

Viết chương trình đọc ba số nguyên m , n từ STDIN hoặc dưới dạng đối số dòng lệnh, in tất cả các góc có thể có của hình chữ nhật có kích thước m × n bằng 2 × 11 × 2 và cuối cùng là số lượng nghiêng hợp lệ.

Dominos của một ốp lát riêng lẻ phải được biểu thị bằng hai dấu gạch ngang ( -) cho 2 × 1 và hai thanh dọc ( |) cho domino 1 × 2 . Mỗi ốp lát (bao gồm cả lát cuối cùng) phải được theo sau bởi một dòng cấp.

Để ghi điểm, bạn cũng phải chấp nhận một cờ từ STDIN hoặc làm đối số dòng lệnh làm cho chương trình của bạn chỉ in số nghiêng hợp lệ, nhưng không phải là nghiêng.

Chương trình của bạn có thể không dài hơn 1024 byte. Nó phải làm việc cho tất cả các đầu vào sao cho m × n ≤ 64 .

(Lấy cảm hứng từ In tất cả các góc domino của hình chữ nhật 4x6 .)

Thí dụ

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

||--
||--

|--|
|--|

--||
--||

||||
||||

5
$ sdt 4 2 scoring
5

Chấm điểm

Điểm của bạn được xác định theo thời gian thực hiện chương trình của bạn cho đầu vào 8 8 với cờ được đặt.

Để biến này thành mã nhanh nhất thay vì thử thách máy tính nhanh nhất , tôi sẽ chạy tất cả các lần gửi trên máy tính của riêng tôi (Intel Core i7-3770, RAM 16 GiB PC3-12800) để xác định điểm chính thức.

Vui lòng để lại hướng dẫn chi tiết về cách biên dịch và / hoặc thực thi mã của bạn. Nếu bạn yêu cầu một phiên bản cụ thể của trình biên dịch / trình thông dịch ngôn ngữ của bạn, hãy đưa ra tuyên bố về hiệu ứng đó.

Tôi bảo lưu quyền để lại các bài nộp không được bảo vệ nếu:

  • Không có trình biên dịch / trình thông dịch miễn phí (như trong bia) cho hệ điều hành của tôi (Fedora 21, 64 bit).

  • Bất chấp những nỗ lực của chúng tôi, mã của bạn không hoạt động và / hoặc tạo đầu ra không chính xác trên máy tính của tôi.

  • Quá trình biên dịch hoặc thực thi mất nhiều hơn một giờ.

  • Mã của bạn hoặc trình biên dịch / trình thông dịch có sẵn duy nhất chứa một cuộc gọi hệ thống đến rm -rf ~hoặc một cái gì đó tương tự như cá.

Bảng xếp hạng

Tôi đã ghi lại tất cả các lần gửi, chạy cả hai phần tổng hợp và thực thi trong một vòng lặp với 10.000 lần lặp để biên dịch và từ 100 đến 10.000 lần lặp để thực hiện (tùy thuộc vào tốc độ của mã) và tính giá trị trung bình.

Đây là kết quả:

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.

Tại sao không biến đây thành một cuộc thi GOLF? :(
orlp

2
Nếu bạn đã đề nghị rằng trong hộp cát, tôi có thể có. Điều đó sẽ cứu CPU của tôi và tôi rất nhiều công việc ...
Dennis

3
@ kirbyfan64sos Theo cách tôi hiểu, chỉ có một loại domino mà bạn có thể xoay. Nếu nó nằm ngang, nó trông như thế này : --. Nếu nó thẳng đứng, nó là hai |, một bên dưới cái kia.
Reto Koradi

1
Thử thách của bạn không tệ. Vấn đề là các lập trình viên hàng đầu của chúng ta quá mạnh. Giải pháp của tôi kiểm tra tính hợp lệ của các hàng và cột ở gần 1 phút cho 6x8.
edc65

1
Tôi nghĩ rằng chiến lược tốt nhất bây giờ là sử dụng lắp ráp và cố gắng lấy một tệp nhị phân nhỏ hơn 1024 byte, để thoát khỏi thời gian phức tạp.
jimmy23013

Câu trả lời:


5

C

Việc thực hiện đơn giản ...

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

Phiên bản gian lận

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

Giải thích về thuật toán nhanh hơn

Nó quét từ trái sang phải và duy trì trạng thái d[i][j], trong đó:

  • ilà trong [0,m), có nghĩa là cột hiện tại.
  • jlà một vectơ bit có kích thước n, trong đó bit sẽ là 1 nếu vị trí tương ứng trong cột iđã bị chiếm giữ trước khi bắt đầu làm việc trên cột này. Tức là nó bị chiếm bởi một nửa bên phải của a --.
  • d[i][j] là tổng số độ nghiêng khác nhau.

Sau đó nói e[i][j]= tổng số d[i][k]nơi bạn có thể đặt domino dọc dựa trên kđể tạo thành a j. e[i][j]sẽ là số lượng nghiêng trong đó mỗi 1 bit jđược chiếm bởi bất cứ thứ gì ngoại trừ nửa bên trái của a --. Điền vào chúng --và bạn sẽ nhận được d[i+1][~j]= e[i][j]. e[m-1][every bit being 1]hoặc d[m][0]là câu trả lời cuối cùng.

Việc triển khai ngây thơ sẽ giúp bạn có được sự phức tạp về thời gian ở đâu đó gần g[n]=2*g[n-1]+g[n-2] = O((sqrt(2)+1)^n)(đã đủ nhanh nếu n = m = 8). Nhưng thay vào đó, trước tiên bạn có thể lặp cho mỗi domino có thể và cố gắng thêm nó vào mọi ốp có thể thêm domino này và hợp nhất kết quả vào mảng ban đầu d(như thuật toán cho bài toán Knapsack). Và điều này trở thành O (n * 2 ^ n). Và mọi thứ khác là chi tiết thực hiện. Toàn bộ mã chạy trong O (m * n * 2 ^ n).


@Dennis Bạn có thể muốn bắt đầu một cuộc thăm dò để thay đổi nó.
jimmy23013

@Dennis Không chắc việc tăng kích thước sẽ giúp ích nhiều. Mặc dù nó làm tăng đáng kể thời gian tính toán, nhưng nó cũng tạo ra sản lượng gấp 100 lần. Nói một cách tương đối, lượng đầu ra thực sự lớn hơn.
Reto Koradi

Phiên bản đầu tiên Thi hành: 0.286 s Biên dịch: 0.053 s Tổng: 0.339 s Phiên bản thứ 2 Thi hành: 0,002 s Tổng hợp: 0,061 s Tổng: 0,063 s (Điều gì vừa xảy ra ở đây?)
Dennis

@Dennis Nó đã sử dụng một thuật toán khác trong O (m * n * 2 ^ n) nếu cờ được đặt.
jimmy23013

1
Thi hành: 190 ms Biên soạn: 68 ms Tổng cộng: 258 ms ( -O1dường như là điểm ngọt ngào. Tôi đã thử tất cả các mức tối ưu hóa.)
Dennis

3

C

Sau một vòng tối ưu hóa và điều chỉnh cho các quy tắc được sửa đổi:

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

Tôi bắt đầu va vào giới hạn độ dài 1024 ký tự, vì vậy tôi phải giảm phần nào khả năng đọc. Tên biến ngắn hơn nhiều, v.v.

Hướng dẫn xây dựng:

> gcc -O2 Code.c

Chạy với đầu ra giải pháp được kích hoạt:

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

Chạy chỉ với số lượng giải pháp:

> ./a.out 8 8 s

Một vài bình luận:

  • Với ví dụ thử nghiệm lớn hơn, tôi muốn tối ưu hóa ngay bây giờ. Trong khi hệ thống của tôi khác (Mac), xung quanh -O2có vẻ tốt.
  • Mã đã chậm hơn trong trường hợp đầu ra được tạo. Đây là một sự hy sinh có ý thức để tối ưu hóa chế độ "chỉ đếm" và để giảm độ dài mã.
  • Sẽ có một vài cảnh báo về trình biên dịch vì thiếu bao gồm và khai báo bên ngoài cho các chức năng hệ thống. Đó là cách dễ nhất để đưa tôi đến dưới 1024 ký tự mà không làm cho mã hoàn toàn không thể đọc được.

Cũng lưu ý rằng mã vẫn tạo ra các giải pháp thực tế, ngay cả trong chế độ "chỉ đếm". Bất cứ khi nào một giải pháp được tìm thấy, vMbitmask chứa một 1cho các vị trí có thanh dọc và một 0cho các vị trí có thanh ngang. Chỉ chuyển đổi bitmask này sang định dạng ASCII và đầu ra thực tế, bị bỏ qua.


@Dennis Phiên bản mới. Thực thi nên không thay đổi, nhưng biên dịch nhanh hơn. Nếu chúng ta cần tối ưu hóa cho thời gian biên dịch, chúng ta không cần tiêu đề hệ thống!
Reto Koradi

@Dennis Cập nhật cho điểm mới và cho một vòng tối ưu hóa. Lưu ý rằng tôi muốn tối ưu hóa ngay bây giờ, một cái gì đó như -O2là tốt.
Reto Koradi

Thực thi: 256 ms Biên dịch: 65 ms Tổng cộng: 321 ms ( -O2dường như là điểm hấp dẫn. Tôi đã thử tất cả các mức tối ưu hóa.)
Dennis

1

C

Khái niệm đầu tiên là tìm tất cả các sắp xếp có thể của domino ngang trong một hàng, lưu trữ chúng r[]và sau đó sắp xếp chúng để đưa ra tất cả các sắp xếp có thể của domino dọc.

Mã để định vị domino ngang trong một hàng được sửa đổi từ câu trả lời này của tôi: /codegolf//a/37888/15599 . Nó chậm đối với các lưới rộng hơn nhưng đó không phải là vấn đề đối với trường hợp 8x8.

Sự đổi mới là trong cách các hàng được lắp ráp. Nếu bảng có số lượng hàng lẻ, nó được xoay 90 độ trong phân tích cú pháp đầu vào, vì vậy giờ đây nó có số lượng hàng chẵn. Bây giờ tôi đặt một số domino dọc trên đường trung tâm. Do tính đối xứng, nếu có ccách sắp xếp các domino còn lại ở nửa dưới, thì cũng phải có ccách sắp xếp các domino còn lại ở nửa trên, có nghĩa là đối với sự sắp xếp của các domino dọc trên đường trung tâm, có những c*cgiải pháp khả thi . Do đó, chỉ phân tích trung tâm cộng với một nửa bảng được phân tích khi chương trình được yêu cầu chỉ in số lượng giải pháp.

f()xây dựng bảng sắp xếp có thể của domino ngang và quét qua các sắp xếp có thể của domino dọc trên đường trung tâm. sau đó nó gọi hàm đệ quy g()điền vào các hàng. Nếu in ấn là bắt buộc, chức năng h()được gọi để làm điều này.

g()được gọi với 3 tham số. ylà hàng hiện tại và dlà hướng (lên hoặc xuống) trong đó chúng tôi đang lấp đầy bảng từ trung tâm ra ngoài. xchứa một bitmap cho biết các domino dọc không đầy đủ từ hàng trước. Tất cả các sắp xếp có thể của domino liên tiếp từ r [] đều được thử. Trong mảng này, 1 đại diện cho một domino dọc và một cặp số không là một domino ngang. Một mục hợp lệ trong mảng phải có ít nhất 1 giây để hoàn thành bất kỳ domino dọc không hoàn chỉnh nào từ hàng cuối cùng : (x&r[j])==x. Nó có thể có nhiều hơn 1 chỉ ra rằng domino dọc mới đang được bắt đầu. Đối với hàng tiếp theo, sau đó, chúng tôi chỉ cần các domino mới vì vậy chúng tôi gọi lại thủ tục với x^r[j].

Nếu một hàng kết thúc đã đạt được và không có domino dọc không hoàn chỉnh treo trên cùng hoặc dưới cùng của bảng x^r[j]==0thì một nửa đã được thành công. Nếu chúng tôi không in, việc hoàn thành nửa dưới và sử dụng c*cđể tính tổng số sắp xếp là đủ. Nếu chúng tôi đang in, sẽ cần phải hoàn thành nửa trên cùng và sau đó gọi chức năng in 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");

}

Lưu ý rằng đầu vào có số lượng hàng lẻ và số cột chẵn được xoay qua 90 độ trong giai đoạn phân tích cú pháp. Nếu điều này không được chấp nhận, chức năng in h()có thể được thay đổi để chứa nó. (EDIT: không bắt buộc, xem ý kiến.)

EDIT: Một chức năng mới e()đã được sử dụng để kiểm tra tính chẵn lẻ của i(tức là số lượng domino nằm trên đường trung tâm.) Tính chẵn lẻ của i(số lượng nửa domino trên đường trung tâm nhô vào mỗi nửa của bảng) phải giống như độ lẻ của tổng số khoảng trắng trong mỗi nửa (được cho bởi n/2) bởi vì chỉ khi đó, domino mới có thể lấp đầy tất cả không gian có sẵn. Chỉnh sửa này giúp loại bỏ một nửa giá trị của i và do đó làm cho chương trình của tôi nhanh gấp đôi.


Thực thi: 18 ms Biên dịch: 50 ms Tổng cộng: 68 ms ( -O0là điểm ngọt ngào cho tổng số. Các tùy chọn khác làm chậm quá trình biên dịch.)
Dennis

Điều này hoặc không bao giờ chấm dứt, hoặc ít nhất là mất một thời gian rất dài, cho đầu vào 32 2 s. Tôi dừng nó sau khoảng 15 phút.
Reto Koradi

@RetoKoradi thực sự, nhưng 2 32 schạy gần như ngay lập tức. Việc quét qua tất cả các domino dọc có thể rất lãng phí cho H=2trường hợp, bởi vì trên thực tế chúng ta đã có tất cả thông tin cần thiết trong đó r[]. Tôi rất hài lòng với thời gian chính thức cho 8 8 sĐây là bản vá cho trường hợp bạn đề cập: 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;Như bạn có thể thấy đoạn trích này sẽ chạy ngay lập tức H=2 với bộ cờ. Thời gian chạy tổng thể sau đó bị giới hạn bởi tòa nhà trong r[]đó chắc chắn có chỗ để cải thiện.
Cấp sông St

Để hoàn thiện, đây là bản vá để tăng đầu ra đúng cách, nếu được yêu cầu: 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);Độ dài mã hóa vẫn dưới 1000 byte và tác động đến thời gian biên dịch nên ở mức tối thiểu. Tôi đã không bao gồm các bản vá này đêm qua vì tôi đã quá mệt mỏi.
Cấp sông St

Tôi định bình luận về điều đó tối qua, nhưng tôi đã quên. Vì việc ghi điểm được thực hiện trên một hình vuông, tôi sẽ không nhấn mạnh vào một thứ tự cụ thể.
Dennis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.