Xây dựng một bộ giải câu đố phía trước


15

Câu đố mặt trước là một câu đố trong đó bạn được yêu cầu xây dựng hình dạng 3 chiều của các khối (thường là khối) với ba góc nhìn trực giao: góc nhìn từ trên xuống, mặt trước và mặt bên.

Ví dụ: được cung cấp chế độ xem trên cùng, phía trước và bên như sau:

Top:        Front:      Side:
. . . .     . . . .     . . . .
. x x .     . x x .     . x x .
. x x .     . x x .     . x x .
. . . .     . . . .     . . . .

In this problem, the side view is taken from the right.

Một khối lập phương 2x2x2 (với tập 8) sẽ thỏa mãn giải pháp này, nhưng nó có thể thực hiện được trong tập 4, nếu chúng ta có cấu trúc lớp sau:

. . . .     . . . .
. x . .     . . x .
. . x .     . x . .
. . . .     . . . .

Ngoài ra còn có một số sắp xếp không thể giải quyết. Lấy ví dụ:

Top:        Front:      Side:
. . . .     . . . .     . . . .
. . . .     . . x .     . . . .
. x . .     . . . .     . x . .
. . . .     . . . .     . . . .

Nếu chế độ xem trên cùng cho biết khối này đứng thứ hai từ bên trái, thì không có cách nào có thể khớp với chế độ xem trước cho biết khối phải đứng thứ ba từ bên trái. Vì vậy, sự sắp xếp này là không thể.


Nhiệm vụ của bạn là xây dựng một chương trình, đưa ra một câu đố trên mặt trước tùy ý 4 x 4, cố gắng giải nó trong một số ít hình khối nhất, hoặc tuyên bố nó không thể giải được.

Chương trình của bạn sẽ lấy đầu vào là một chuỗi 48 bit, đại diện cho các khung nhìn trên cùng, phía trước và bên. Chúng có thể ở bất kỳ định dạng nào bạn muốn (chuỗi 6 byte, chuỗi 0 và 1, số hex 12 chữ số, v.v.), nhưng thứ tự của các bit phải ánh xạ như sau:

Top: 0x00   Front: 0x10 Side: 0x20
0 1 2 3     0 1 2 3     0 1 2 3
4 5 6 7     4 5 6 7     4 5 6 7
8 9 a b     8 9 a b     8 9 a b
c d e f     c d e f     c d e f

Nói cách khác, các bit đi theo thứ tự từ trái sang phải, từ trên xuống dưới, ở trên cùng, sau đó trước, sau đó xem bên.

Chương trình của bạn sau đó sẽ xuất ra một chuỗi 64 bit cho biết các hình khối trong lưới 4x4x4 được điền vào hoặc chỉ ra rằng lưới không thể giải được.


Chương trình của bạn sẽ được ghi điểm bằng cách chạy pin 1.000.000 trường hợp thử nghiệm.

Dữ liệu thử nghiệm sẽ được tạo bằng cách lấy các giá trị băm MD5 của các số nguyên "000000" đến "999999" làm chuỗi, trích xuất 48 bit đầu tiên (12 hình lục giác) của mỗi băm này và sử dụng chúng làm đầu vào cho mặt trước trên cùng câu đố bên. Ví dụ, đây là một số đầu vào kiểm tra và các câu đố mà chúng tạo ra:

Puzzle seed: 000000   hash: 670b14728ad9
Top:        Front:      Side:
. x x .     . . . x     x . . .
x x x x     . x . x     x . x .
. . . .     . x x x     x x . x
x . x x     . . x .     x . . x

Puzzle seed: 000001   hash: 04fc711301f3
Top:        Front:      Side:
. . . .     . x x x     . . . .
. x . .     . . . x     . . . x
x x x x     . . . x     x x x x
x x . .     . . x x     . . x x

Puzzle seed: 000157   hash: fe88e8f9b499
Top:        Front:      Side:
x x x x     x x x .     x . x x
x x x .     x . . .     . x . .
x . . .     x x x x     x . . x
x . . .     x . . x     x . . x

Hai cái đầu tiên không thể giải quyết được, trong khi cái cuối cùng có một giải pháp với các lớp sau, từ trước ra sau:

x . . .   . . . .   x x x .   x x x .
. . . .   x . . .   . . . .   . . . .
x . . .   . . . .   . . . .   x x x x
x . . .   . . . .   . . . .   x . . x

There are a total of 16 blocks here, but it can probably be done in less.

Điểm số của chương trình của bạn sẽ được xác định theo các tiêu chí sau, theo thứ tự ưu tiên giảm dần:

  • Số lượng các trường hợp được giải quyết cao nhất.
  • Số khối thấp nhất cần thiết để giải quyết các trường hợp đó.
  • Mã ngắn nhất tính bằng byte.

Bạn phải tự mình nộp và tính điểm, điều này đòi hỏi chương trình của bạn phải có khả năng chạy qua tất cả 1.000.000 trường hợp kiểm tra.


Tôi đã học được trong khi cố gắng tạo ra các trường hợp thử nghiệm cho vấn đề này rằng nhiều trường hợp không thể giải quyết hơn là không. Tôi tự hỏi làm thế nào điều này sẽ bật ra.
Joe Z.

Nếu đó là một vấn đề tối ưu hóa, cần có giới hạn thời gian, vì vậy mọi người không nên ép buộc.
isaacg 28/03/2015

Tuy nhiên, thời gian giới hạn là lâu để họ kiểm tra nó. Một giải pháp mất mãi mãi để chạy sẽ không bao giờ tạo ra kết quả có thể được đăng ở đây. Đó là cách tất cả các vấn đề tối ưu hóa tôi viết công việc.
Joe Z.


1
@JoeZ. orlp là đúng. Tôi đã có một lỗi trong chuyển đổi câu đố md5 của tôi. 551 câu đố có thể giải được trong 00000-99999 và 5360 câu đố có thể giải được trong 000000-999999.
Jakube

Câu trả lời:


5

Python: 5360 trường hợp thử nghiệm được giải quyết bằng cách sử dụng 69519 khối, 594 byte

Đây phải là giá trị tối ưu.

Tiếp cận:

Trước tiên tôi sẽ kiểm tra xem trường hợp thử nghiệm có thể giải quyết được không. Tôi làm điều này bằng cách khởi tạo một danh sách có độ dài 64 theo từng cái và đặt tất cả các vị trí thành 0, nếu có pixel tương ứng của ba chế độ xem bằng không. Sau đó, tôi xem đơn giản câu đố từ cả 3 hướng và xem các khung nhìn có giống với các khung nhìn đầu vào không. Nếu có bằng nhau, câu đố có thể giải được (chúng tôi đã tìm ra giải pháp tồi tệ nhất), nếu không thì không thể giải được.

Sau đó, tôi thực hiện một cách tiếp cận chi nhánh và liên kết để tìm giải pháp tối ưu.

Phân nhánh: Tôi có một hàm đệ quy. Độ sâu đệ quy cho biết có bao nhiêu giá trị đã được cố định. Trong mỗi cuộc gọi của hàm, tôi gọi chính nó hai lần, nếu giá trị tại chỉ mục hiện tại là 1 (giá trị này có thể là 0 hoặc 1 trong giải pháp tối ưu) hoặc một lần, nếu giá trị bằng 0 (nó phải bằng 0 trong giải pháp tối ưu).

Bounding: Tôi sử dụng hai chiến lược khác nhau ở đây.

  1. Tôi tính toán các khung nhìn từ 3 phía trong mỗi lệnh gọi hàm và xem Nếu chúng vẫn khớp với các giá trị đầu vào. Nếu chúng không khớp, tôi không gọi hàm đệ quy.

  2. Tôi giữ giải pháp tốt nhất trong bộ nhớ. Nó đã có nhiều cái cố định trong nhánh hiện tại hơn là giải pháp tốt nhất, tôi đã có thể đóng nhánh đó. Ngoài ra, tôi có thể ước tính giới hạn dưới cho số lượng khối được kích hoạt, không cố định. Và điều kiện trở thành#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Đây là mã Python. Nó định nghĩa một hàm fcó 3 danh sách chứa 1 và 0 và trả về 0 (không thể giải được) hoặc danh sách 1 và 0 đại diện cho giải pháp tối ưu.

S=sum;r=range
def f(t,f,s):
 for i in r(4):s[4*i:4*i+4]=s[4*i:4*i+4][::-1]
 c=[min(t[i%16],f[(i//16)*4+i%4],s[i//4])for i in r(64)]
 m=lambda:([int(S(c[i::16])>0)for i in r(16)],[int(S(c[i+j:i+j+16:4])>0)for i in r(0,64,16)for j in r(4)],[int(S(c[i+j:i+j+4])>0)for i in r(0,64,16)for j in r(0,16,4)])==(t,f,s)
 Z=[65,0];p=[]
 def g(k):
  if k>63and S(c)<Z[0]:Z[:]=[S(c),c[:]]
  if k>63or S(c[:k])+p[k]>=Z[0]:return
  if c[k]:c[k]=0;m()and g(k+1);c[k]=1
  m()and g(k+1)
 for i in r(64):h,R=(i//16)*4+4,(i//4)%4;p+=[max(S(f[h:]),S(s[h:]))+max((R<1)*S(f[h+i%4-3:h]),S(s[h+R-3:h]))]
 g(0);return Z[1]

Độ dài mã là 596 byte / ký tự. Và đây là khung kiểm tra:

from hashlib import md5
from time import time

N = 1000000
start=time();count=blocks=0
for n in range(N):
 bits = list(map(int,"{:048b}".format(int(md5("{:06}".format(n).encode("utf-8")).hexdigest()[:12], 16))))
 result = f(bits[:16], bits[16:32], bits[32:])
 if result:
  count += 1
  blocks += sum(result)
  print("Seed: {:06}, blocks: {}, cube: {}".format(n, sum(result), result))
print()
print("{} out of {} puzzles are solvable using {} blocks.".format(count, N, blocks))
print("Total time: {:.2f} seconds".format(time()-start))

Bạn có thể tìm thấy một phiên bản vô văn hóa của chương trình ở đây . Nó cũng nhanh hơn một chút.

Các kết quả:

5360 trong số 1000000 câu đố có thể giải được. Tổng cộng chúng ta cần 69519 khối. Số lượng khối thay đổi từ 6 khối đến 18 khối. Câu đố khó nhất mất khoảng 1 giây để giải. Đó là câu đố với hạt giống "347177", trông giống như

Top:      Front:    Side:
x x . .   x x x x   x . x .
x x x x   x x x x   x x x x
x x x x   x x x x   x x x x
x x . x   x x x x   x . x x

và có một giải pháp tối ưu với 18 khối. Sau đây là một vài từ trên cùng cho mỗi lớp:

Top 1:    Top 2:    Top 3:    Top 4:
. . . .   . x . .   . x . .   x . . .
. . x x   . . x .   x . . .   . x x .
. . . .   . . . x   x x x .   . . . .
x x . .   x . . .   . . . x   . . . x

Tổng thời gian chạy cho tất cả các trường hợp thử nghiệm là khoảng 90 giây. Tôi đã sử dụng PyPy để thực hiện chương trình của mình. CPython (trình thông dịch Python mặc định) chậm hơn một chút, nhưng cũng giải quyết tất cả các câu đố chỉ trong 7 phút.

Bạn có thể tìm thấy đầu ra hoàn chỉnh ở đây : Đầu ra là tự giải thích. Ví dụ: đầu ra cho câu đố trên là:

Seed: 347177, blocks: 18, cube: [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1]

3

5360 trường hợp được giải quyết với 69519 khối; 923 byte

Điều này cũng là tối ưu. Gọi với một loạt các số và số không. Ném a NullPointerExceptioncho đầu vào không hợp lệ. Một số hiệu quả được hy sinh để chơi golf nó. Nó vẫn hoàn thành trong một thời gian hợp lý cho tất cả 1000000 đầu vào thử nghiệm.

import java.util.*;int[]a(int[]a){a b=new a(a);b=b.b(64);return b.d();}class a{List<int[]>a=new ArrayList();List b=new ArrayList();int c;a(int[]d){int e=0,f,g,h,i[]=new int[48];for(;e<64;e++){f=e/16;g=(e%16)/4;h=e%4;if(d[f+12-4*h]+d[16+f+g*4]+d[32+h+g*4]>2){i[f+12-4*h]=i[16+f+g*4]=i[32+h+g*4]=1;a.add(new int[]{f,g,h});c++;}}if(!Arrays.equals(d,i))throw null;b=f();}a(){}a b(int d){if(c-b.size()>d|b.size()<1)return this;a e=c();e.a.remove(b.get(0));e.b.retainAll(e.f());e.c--;e=e.b(d);d=Math.min(e.c,d);a f=c();f=f.b(d);return e.c>f.c?f:e;}a c(){a c=new a();c.a=new ArrayList(a);c.b=new ArrayList(b);c.b.remove(0);c.c=this.c;return c;}int[]d(){int[]d=new int[64];for(int[]e:a)d[e[2]*16+e[1]*4+e[0]]=1;return d;}List f(){List d=new ArrayList();int e,f,g;for(int[]h:a){e=0;f=0;g=0;for(int[]p:a)if(p!=h){e|=h[0]==p[0]&h[1]==p[1]?1:0;f|=h[0]==p[0]&h[2]==p[2]?1:0;g|=h[1]==p[1]&h[2]==p[2]?1:0;}if(e+f+g>2)d.add(h);}return d;}}

Chiến lược:

Tôi thực sự đã từng chơi câu đố này khá nhiều khi tôi lên 10. Điều này sử dụng cách tiếp cận của tôi.

Bước 1:

Tạo thành khối với hầu hết các khối phù hợp với các khung nhìn đã cho.

Bước 2:

Tạo một danh sách các mảnh rời. (Bất kỳ mảnh nào có một mảnh khác trên hàng của nó, cột của nó và chùm tia của nó.)

Bước 3:

Kiểm tra mọi cách có thể để giữ hoặc loại bỏ từng mảnh rời. (Thông qua lực lượng đệ quy với việc cắt tỉa.)

Bước 4:

Giữ khối hợp lệ tốt nhất.

Ung dung:

int[] main(int[] bits) {
    Cube cube = new Cube(bits);
    cube = cube.optimize(64);
    return cube.bits();
}

class Cube {

    List<int[]> points = new ArrayList();
    List removablePoints = new ArrayList();
    int size;

    Cube(int[] bits) {
        int i = 0,x,y,z,placed[] = new int[48];
        for (; i < 64; i++) {
            x = i / 16;
            y = (i % 16) / 4;
            z = i % 4;
            if (bits[x + 12 - 4 * z] + bits[16 + x + y * 4] + bits[32 + z + y * 4] > 2) {
                placed[x + 12 - 4 * z] = placed[16 + x + y * 4] = placed[32 + z + y * 4] = 1;
                points.add(new int[]{x, y, z});
                size++;
            }
        }

        if (!Arrays.equals(bits, placed))
            throw null;

        removablePoints = computeRemovablePoints();
    }

    Cube() {
    }

    Cube optimize(int smallestFound) {
        if (size - removablePoints.size() > smallestFound | removablePoints.size() < 1)
            return this;

        Cube cube1 = duplicate();
        cube1.points.remove(removablePoints.get(0));

        cube1.removablePoints.retainAll(cube1.computeRemovablePoints());
        cube1.size--;

        cube1 = cube1.optimize(smallestFound);
        smallestFound = Math.min(cube1.size, smallestFound);

        Cube cube2 = duplicate();

        cube2 = cube2.optimize(smallestFound);

        return cube1.size > cube2.size ? cube2 : cube1;

    }

    Cube duplicate() {
        Cube c = new Cube();
        c.points = new ArrayList(points);
        c.removablePoints = new ArrayList(removablePoints);
        c.removablePoints.remove(0);
        c.size = size;
        return c;
    }

    int[] bits() {
        int[] bits = new int[64];
        for (int[] point : points)
            bits[point[2] * 16 + point[1] * 4 + point[0]] = 1;
        return bits;
    }

    List computeRemovablePoints(){
        List removablePoints = new ArrayList();
        int removableFront, removableTop, removableSide;
        for (int[] point : points) {
            removableFront = 0;
            removableTop = 0;
            removableSide = 0;
            for (int[] p : points)
                if (p != point) {
                    removableFront |= point[0] == p[0] & point[1] == p[1] ? 1 : 0;
                    removableTop |= point[0] == p[0] & point[2] == p[2] ? 1 : 0;
                    removableSide |= point[1] == p[1] & point[2] == p[2] ? 1 : 0;
                }
            if (removableFront + removableTop + removableSide > 2)
                removablePoints.add(point);
        }
        return removablePoints;
    }

    public String toString() {

        String result = "";
        int bits[] = bits(),x,y,z;

        for (z = 0; z < 4; z++) {
            for (y = 0; y < 4; y++) {
                for (x = 0; x < 4; x++) {
                    result += bits[x + 4 * y + 16 * z] > 0 ? 'x' : '.';
                }
                result += System.lineSeparator();
            }
            result += System.lineSeparator();
        }

        return result;

    }
}

Chương trình đầy đủ:

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Example cube:
 *
 * origin
 * |   ........
 * |  .      ..
 * | . top  . .
 * v.      .  .
 * ........   .  <- side
 * .      .  .
 * . front. .
 * .      ..
 * ........
 *
 *     / z
 *    /
 *  /
 * .-----> x
 * |
 * |
 * |
 * V y
 */

public class PPCG48247 {

    public static void main(String[] args) throws Exception{
        MessageDigest digest = MessageDigest.getInstance("MD5");
        int totalSolved = 0;
        int totalCubes = 0;

        for (int i = 0; i < 1000000; i++){
            byte[] input = String.format("%06d", i).getBytes();

            byte[] hash = digest.digest(input);
            int[] bits = new int[48];

            for (int j = 0, k = 0; j < 6; j++, k += 8){
                byte b = hash[j];
                bits[k] = (b >> 7) & 1;
                bits[k + 1] = (b >> 6) & 1;
                bits[k + 2] = (b >> 5) & 1;
                bits[k + 3] = (b >> 4) & 1;
                bits[k + 4] = (b >> 3) & 1;
                bits[k + 5] = (b >> 2) & 1;
                bits[k + 6] = (b >> 1) & 1;
                bits[k + 7] = b & 1;
            }

            try {
                int[] solution = new PPCG48247().a(bits);
                totalSolved++;
                for (int b : solution){
                    totalCubes += b;
                }
            } catch (NullPointerException ignored){}

        }
        System.out.println(totalSolved);
        System.out.println(totalCubes);
    }

    int[] main(int[] bits) {
        Cube cube = new Cube(bits);
        cube = cube.optimize(64);
        return cube.bits();
    }

    class Cube {

        List<int[]> points = new ArrayList();
        List removablePoints = new ArrayList();
        int size;

        Cube(int[] bits) {
            int i = 0,x,y,z,placed[] = new int[48];
            for (; i < 64; i++) {
                x = i / 16;
                y = (i % 16) / 4;
                z = i % 4;
                if (bits[x + 12 - 4 * z] + bits[16 + x + y * 4] + bits[32 + z + y * 4] > 2) {
                    placed[x + 12 - 4 * z] = placed[16 + x + y * 4] = placed[32 + z + y * 4] = 1;
                    points.add(new int[]{x, y, z});
                    size++;
                }
            }

            if (!Arrays.equals(bits, placed))
                throw null;

            removablePoints = computeRemovablePoints();
        }

        Cube() {
        }

        Cube optimize(int smallestFound) {
            if (size - removablePoints.size() > smallestFound | removablePoints.size() < 1)
                return this;

            Cube cube1 = duplicate();
            cube1.points.remove(removablePoints.get(0));

            cube1.removablePoints.retainAll(cube1.computeRemovablePoints());
            cube1.size--;

            cube1 = cube1.optimize(smallestFound);
            smallestFound = Math.min(cube1.size, smallestFound);

            Cube cube2 = duplicate();

            cube2 = cube2.optimize(smallestFound);

            return cube1.size > cube2.size ? cube2 : cube1;

        }

        Cube duplicate() {
            Cube c = new Cube();
            c.points = new ArrayList(points);
            c.removablePoints = new ArrayList(removablePoints);
            c.removablePoints.remove(0);
            c.size = size;
            return c;
        }

        int[] bits() {
            int[] bits = new int[64];
            for (int[] point : points)
                bits[point[2] * 16 + point[1] * 4 + point[0]] = 1;
            return bits;
        }

        List computeRemovablePoints(){
            List removablePoints = new ArrayList();
            int removableFront, removableTop, removableSide;
            for (int[] point : points) {
                removableFront = 0;
                removableTop = 0;
                removableSide = 0;
                for (int[] p : points)
                    if (p != point) {
                        removableFront |= point[0] == p[0] & point[1] == p[1] ? 1 : 0;
                        removableTop |= point[0] == p[0] & point[2] == p[2] ? 1 : 0;
                        removableSide |= point[1] == p[1] & point[2] == p[2] ? 1 : 0;
                    }
                if (removableFront + removableTop + removableSide > 2)
                    removablePoints.add(point);
            }
            return removablePoints;
        }

        public String toString() {

            String result = "";
            int bits[] = bits(),x,y,z;

            for (z = 0; z < 4; z++) {
                for (y = 0; y < 4; y++) {
                    for (x = 0; x < 4; x++) {
                        result += bits[x + 4 * y + 16 * z] > 0 ? 'x' : '.';
                    }
                    result += System.lineSeparator();
                }
                result += System.lineSeparator();
            }

            return result;

        }
    }

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