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


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.