Створіть головоломку для головоломки зверху спереду


15

Головоломка зверху на лицьовій стороні - це головоломка, де потрібно створити 3-D форму (як правило, кубічних) блоків з трьома ортогональними видами: вид зверху, вид спереду та вид збоку.

Наприклад, подано вид зверху, спереду та збоку таким чином:

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.

Куб 2x2x2 (об'ємом 8) задовольнив би це рішення, але це можливо в обсязі 4, якщо у нас є така структура шару:

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

Є також деякі нерозв'язні домовленості. Візьмемо, наприклад:

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

Якщо вигляд зверху говорить, що блок знаходиться зліва зліва, немає способу, який може відповідати виду спереду, який говорить, що блок повинен бути третім зліва. Тож ця домовленість неможлива.


Ваше завдання - створити програму, яка, задаючи довільну головоломку 4х4 зверху спереду, намагається вирішити її в найменшій кількості кубів або оголошує її нерозв'язною.

Ваша програма прийме як вхід серію з 48 біт, представляючи вигляд зверху, спереду та збоку. Вони можуть бути в будь-якому бажаному форматі (6-байтовий рядок, рядок 0 і 1, 12-значний шістнадцятковий номер тощо), але порядок бітів повинен відображатись таким чином:

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

Іншими словами, біти йдуть у лівому та правому порядку, зверху вниз, згори, потім спереду, потім вид збоку.

Тоді ваша програма виведе або серію з 64 біт, що вказують на куби в сітці 4x4x4, які заповнені, або вказують на те, що сітка нерозв'язна.


Ваша програма буде оцінена за допомогою батареї в 1 000 000 тестових випадків.

Дані тесту будуть генеровані, приймаючи хеші MD5 цілих чисел "000000" до "999999" як рядки, витягуючи перші 48 біт (12 шестикутників) кожного з цих хешів, і використовуючи їх як вхідні дані для верхнього фронту- бічна головоломка. Як приклад, ось деякі тестові входи та загадки, які вони генерують:

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

Перші два є нерозв'язними, тоді як останній має рішення з наступними шарами, спереду до спини:

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.

Оцінка вашої програми визначатиметься за наступними критеріями у порядку зменшення пріоритетності:

  • Найбільша кількість вирішених справ.
  • Найменша кількість блоків, необхідних для вирішення цих випадків.
  • Найкоротший код у байтах.

Ви повинні подати та обчислити бал самостійно, що вимагає, щоб ваша програма змогла пройти всі 1000000 тестових випадків.


Дізнавшись, намагаючись створити тестові випадки для цієї проблеми, я дізнався, що більше випадків нерозв’язувані, ніж ні. Цікаво, як це вийде.
— Джо З.

Якщо це проблема оптимізації, повинен бути обмежений час, щоб люди не змушували її жорстоко.
— isaacg

Однак для встановлення цього терміну не вистачає часу. Рішення, яке вічно запускається, ніколи не дасть результату, який можна розмістити тут. Ось так працюють усі проблеми з оптимізацією, які я пишу.
— Джо З.


1
@JoeZ. orlp має рацію. У мене була помилка в перетворенні md5 на головоломку. 551 розв’язуваних пазлів у 00000-99999 та 5360 розв’язуваних головоломок у 000000-999999.
— Якубе

Відповіді:


5

Python: 5360 тестових випадків вирішені за допомогою 69519 блоків, 594 байтів

Це повинні бути оптимальні значення.

Підхід:

Спочатку я перевірю, чи справді вирішується тестовий кейс. Я роблю це, ініціалізуючи список довжиною 64 на одиниці і встановлюючи всі позиції до нуля, якщо там відповідний піксель з трьох представлень дорівнює нулю. Потім я просто переглядаю головоломку з усіх 3-х напрямків і дивлюся, чи перегляди збігаються з видами введення. Якщо ви рівні, головоломка вирішується (ми вже знайшли найгірше рішення), інакше вона нерозв’язна.

Тоді я роблю галузевий підхід для пошуку оптимального рішення.

Відділення: мене є рекурсивна функція. Глибина рекурсії говорить про те, скільки значень уже зафіксовано. У кожному виклику функції я дзвоню собі двічі, якщо значення в поточному індексі дорівнює 1 (це значення може бути 0 або 1 в оптимальному рішенні), або один раз, якщо значення дорівнює 0 (воно має бути нульовим в оптимальне рішення).

Обмеження: тут я використовую дві різні стратегії.

  1. Я обчислюю перегляди з трьох сторін у кожному виклику функції та дивлюсь, чи вони все ще відповідають вхідним значенням. Якщо вони не відповідають, я не називаю функцію рекурсивно.

  2. Я зберігаю найкраще рішення в пам’яті. Це там уже більше фіксованих в поточній гілці, ніж у найкращому рішенні, я вже можу закрити цю гілку. Додатково я можу оцінити нижню межу кількості активованих блоків, які не є фіксованими. І умова стає#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Ось код Python. Він визначає функціюf яка очікує 3 списку, що містять 1s і 0s, і повертає або 0 (не вирішується), або список 1s і 0s, що представляють оптимальне рішення.

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]

Довжина коду - 596 байт / символів. І ось рамки тесту:

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

Ви можете знайти неперевершену версію програми тут . Це також трохи швидше.

Результати:

5360 із 1000000 головоломок вирішувані. Загалом нам потрібно 69519 блоків. Кількість блоків коливається від 6 блоків до 18 блоків. Найскладніша головоломка потребувала приблизно 1 секунди для вирішення. Це загадка з насінням "347177", яка виглядає так

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

і має оптимальне рішення з 18 кубів. Далі наведено декілька зверху для кожного з шарів:

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

Загальний час роботи для всіх тестових випадків становив близько 90 секунд. Я використовував PyPy для виконання своєї програми. CPython (інтерпретатор Python за замовчуванням) трохи повільніше, але також вирішує всі головоломки всього за 7 хвилин.

Ви можете знайти повний висновок тут : Вихід не пояснює себе. Наприклад, вихід для головоломки вище:

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 справ, вирішених 69519 блоками; 923 байт

Це також оптимально. Дзвінок з масивом одиниць і нулів. Кидає а NullPointerExceptionдля недійсного введення. Деяка ефективність приносяться в жертву гольфу. Він все ще завершується протягом розумного часу для всіх 1000000 вхідних тестів.

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

Стратегія:

Насправді я дуже часто грав у цю головоломку, коли мені було 10 років. Це використовує мій підхід.

Крок 1:

Сформуйте кубик з більшості блоків, які відповідають заданим видам.

Крок 2:

Створіть список знімних фрагментів. (Будь-яка деталь із цим має ще один шматок на рядку його в, стовпець його в, а промінь - в.)

Крок 3:

Перевірте всі можливі способи зберегти або вийняти кожен знімний елемент. (Через рекурсивну грубу силу з обрізанням.)

Крок 4:

Зберігайте найкращий дійсний куб.

Безголівки:

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;

    }
}

Повна програма:

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;

        }
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.