Я хочу знать, могу ли я, учитывая случайно сгенерированную грань кубика Рубика, определить, соответствует ли эта грань (по крайней мере) одной решаемой конфигурации куба. Может быть, каждую случайную грань можно сопоставить с разрешимым кубом, а может и нет, я тоже не уверен в этом.

Я подумал, что для фиксированной случайной грани было бы неплохо построить остальную часть куба таким образом, чтобы она была разрешимой. Если я могу это сделать, то лицо действительное, в противном случае - нет.

Для этого мне нужно было бы реализовать алгоритм, но я действительно не знаю, с чего начать.

Какие-либо предложения?

1
dancab 20 Янв 2021 в 13:20

2 ответа

Лучший ответ

Может быть, каждую случайную грань можно сопоставить с решаемым кубом

Да, это возможно. Есть даже множество оставшихся возможностей для граней на других сторонах куба.

Я подумал, что для фиксированной случайной грани было бы неплохо построить остальную часть куба таким образом, чтобы она была разрешимой.

Конечно. Самый простой способ добиться этого - попытаться получить случайную конфигурацию граней, начиная с решенного куба. Как человек, выполняйте движения, которые приводят к правильным цветам, чтобы в конечном итоге сформировать заданный цветовой узор одного конкретного лица.

Это всегда будет возможно. И, как следствие, это означает, что вы нашли состояние куба (с этой конкретной гранью), которое может быть решено, поскольку решение состоит в обращении вспять ходов, которые вы сделали, чтобы добраться туда.

Подвести нужные цвета к данному лицу не так уж и сложно. Начните с центральной части лица. Если это не так, просто поверните весь куб, пока он не окажется там.

Затем сосредоточьтесь на каждом из краев. Не так уж и сложно привести края на место, не касаясь уже имеющихся граней.

Наконец проделайте то же самое с углами. Это немного сложнее, так как теперь вам нужно удерживать все четыре края на месте и все углы, которые уже хорошо расположены.

Реализация

Я подумал, что надо попробовать. Итак, ниже я реализовал простой класс Rubik. Экземпляр представляет собой куб, который по умолчанию находится в решенном состоянии. К нему можно применить один или несколько ходов. Любой возможный ход на самом деле представляет собой перестановку 54 стикеров, поэтому все возможные ходы кодируются таким образом.

Затем добавляется функция makeTopFace, специфичная для этой задачи: она принимает желаемый шаблон в качестве аргумента, который представляет собой массив из 9 значений, каждое из которых представляет цвет наклейки в этих положениях верхней грани куба:

0  1  2
3  4  5 
6  7  8

Функция будет использовать экземпляр Rubik для выполнения движений, чтобы установить одну наклейку за другой. В конце функция возвращает последовательность ходов, которые привели куб в желаемое состояние. Не было предпринято никаких усилий, чтобы сделать этот список ходов как можно короче, поскольку здесь нас интересует только то, возможно ли это.

Основная программа использует этот список ходов, чтобы снова создать куб и отобразить его (развернутый) на экране в следующем формате:

(left side) (upper side)
            (front side) (right side)
                         (down side)  (back side)

Ввод можно вводить в интерактивном режиме, щелкая по наклейкам на верхней стороне (которая является второй стороной в плоском представлении). Один щелчок меняет желаемый цвет на другой (циклический). Таким образом, вы можете просто щелкнуть эти наклейки, чтобы создать желаемую конфигурацию. Решение вычисляется по мере того, как вы щелкаете мышью, поэтому вы видите немедленный результат: действительный куб, окружающий сторону, которую вы настроили, вместе со списком перемещений с использованием обозначений, как на Википедия.

Итак, если вы возьмете собранный куб в руки, с синей стороной впереди, белой вверху и красной слева, и примените ходы, как они появляются в этом приложении, вы получите желаемую конфигурацию. на верхней грани вашего куба.

Вот исполняемый фрагмент, созданный на JavaScript:

// A simple Rubik class. An instance represents a cube that can mutate by applying moves.
class Rubik {
    static init() {
        // Define a unique letter for each of the stickers
        this.stickers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0abcdefghijklmnopqrstuvwxyz1";
        let codes = {
            // Define three moves as explicit permutation of 54 stickers:
            L: "MGAyEFNHBzKLOIC1QRDTUVWXJZ0abcPefghijklmnopqrstuSYdvwx",
            M: "ABCDsFGHIJtLMNOPuRSEUVWXYK0abcdQfghijklmnoTZepqrvwxyz1",
            z: "lrxABCkqwGHIjpvMNOdYSPJDeZTQKEf0URLFVWXou1abcntzghimsy",
            // Define all other moves as combinations of already defined moves
            x: "L'M'z2Lz2",
            y: "zxz'",
            U: "z'Lz",
            F: "yLy'",
            R: "z2Lz2",
            D: "zLz'",
            B: "y'Ly",
            E: "zMz'",
            S: "yMy'",
            l: "LM",
            u: "UE'",
            f: "FS",
            r: "RM'",
            d: "DE",
            b: "BS'",
        };
        // Register the above moves, together with their doubles and opposites:
        for (let [move, code] of Object.entries(codes)) {
            this[move] = code.length === 54 ? this.fromCode(code) : new Rubik().apply(code);
            this[move+"2"] = this[move].clone().apply(this[move]);
            this[move+"'"] = this[move+"2"].clone().apply(this[move]);
        }
        this.stickerColor = `
          LLLUUU
          LLLUUU
          LLLUUU
             FFFRRR
             FFFRRR
             FFFRRR
                DDDBBB
                DDDBBB
                DDDBBB`.match(/\S/g);
    }
    static fromCode(code) {
        return new Rubik(Array.from(code, c => Rubik.stickers.indexOf(c)))
    }
    constructor(permutation=Array(54).keys()) { // Optional argument: a permutation array with 54 entries
        if (!Rubik.stickers) Rubik.init();
        // We identify 54 stickers and 54 possible locations with the same numbering:
        this.state = [...permutation];
        // Index = address at fixed location in space. 
        // Value = sticker, i.e. the home-address of the sticker at this address.
        this.log = ""; // Keep track of moves that have been applied to this cube
    }
    clone() {
        return Object.assign(new Rubik, { state: [...this.state] });
    }
    apply(other) {
        if (typeof other === "string") {
            this.log += other;
            for (let move of other.match(/\w['2]?/g) || []) {
                this.apply(Rubik[move]);
            }
        } else {
            this.state = other.state.map(orig => this.state[orig]);
        }
        return this;
    }
    getLog() {
        // Remove the most obvious cases of neutralising moves
        return this.log.replace(/(\w)'/g, "$1$1$1")
                       .replace(/(\w)2/g, "$1$1")
                       .replace(/(\w)\1{3}/g, "") // removal
                       .replace(/(\w)\1{2}/g, "$1'")
                       .replace(/(\w)\1/g, "$12");
    }
    toCode() {
        return this.state.map(i => Rubik.stickers[i]).join("");
    }
    toString() {
        return this.state.map((sticker, i) => 
            (i%6 ? "" : "\n".padEnd(1 + Math.floor(i / 18) * 3)) + Rubik.stickerColor[sticker]
        ).join("").slice(1);
    }
    toHtml() {
        return "<table><tr>" 
            + this.toString().replace(/./gs, m => m === "\n" ? '</tr><tr>' : `<td class="${m}"><\/td>`)
            + "</tr></table>";
    }
}

// Specific function for this question.
// Takes an array with 9 "colors" (from "LUFRDB"), which represents a target 
//  configuration of the upper side.
// This function will return moves that turn a solved cube into a cube
//  that has the upper side look like the given pattern.
function makeTopSide(topSide) {
    let cube = new Rubik;
    // Center:
    let k = [7, 25, 28, 43, 46].map(i => Rubik.stickerColor[cube.state[i]]).indexOf(topSide[4]);
    if (k > -1) { // Need to turn the cube to bring the right center piece into position
        cube.apply(["z", "x", "z'", "z2", "x'"][k]);
    }
    // Edges:
    for (let j of [7, 5, 1, 3]) { // For each edge
        let color = topSide[j]; // Get its desired color
        if (Rubik.stickerColor[cube.state[16]] !== color) {
            for (let i = 0; i < 4; i++) {
                let k = [13, 42, 27, 34].map(i => Rubik.stickerColor[cube.state[i]]).indexOf(color);
                if (k > -1) {
                    cube.apply(["F", "F2", "F'", "RF'R'"][k]);
                    break;
                }
                cube.apply("d");
            }
            if (Rubik.stickerColor[cube.state[19]] === color) {
                cube.apply("Fd'F");
            }
        }
        cube.apply("y"); // Turn the next edge into view
    }
    // Corners:
    for (let j of [8, 2, 0, 6]) { // For each corner:
        let color = topSide[j]; // Get its desired color
        if (Rubik.stickerColor[cube.state[17]] !== color) {
            for (let i = 0; i < 4; i++) {
                let k = [32, 33, 36].map(i => Rubik.stickerColor[cube.state[i]]).indexOf(color);
                if (k > -1) {
                    cube.apply(["FDF'", "R'D'R", "R'DRFD2F'"][k]);
                    break;
                }
                cube.apply("D");
            }
            let k = cube.state.slice(20, 22).map(st => Rubik.stickerColor[st]).indexOf(color);
            if (k > -1) {
                cube.apply(["R'DRFDF'", "FD'F'R'D'R"][k]);
            }
        }
        cube.apply("y"); // Turn the next corner into view
    }
    return cube.getLog();
}

// I/O handling

let output = document.getElementById("output");
let log = document.getElementById("log");
let topSide = [..."UUUUUUUUU"];

function display(cube) {
    output.innerHTML = cube.toHtml();
    log.textContent = cube.getLog();
}
display(new Rubik);

function changeSticker(i) {
    // Change the color of the clicked sticker
    topSide[i] = "UFRDBL"["LUFRDB".indexOf(topSide[i])];
    // Find out which moves generate a cube that has this upper side pattern
    let moves = makeTopSide(topSide);
    let cube = new Rubik().apply(moves);
    display(cube);
}

output.addEventListener("click", function (e) {
    let td = e.target;
    // Only allow changing stickers on the upper side of the cube
    if (td.tagName !== "TD" || td.cellIndex < 3 || td.parentNode.rowIndex > 2) return;
    changeSticker(td.cellIndex - 3 + td.parentNode.rowIndex * 3);
});
#output td { width: 15px; height: 15px }
#output .L { background: red }
#output .U { background: #eee }
#output .F { background: blue }
#output .R { background: orange }
#output .D { background: yellow }
#output .B { background: green }

#output tr:first-child td:nth-child(4),
#output tr:first-child td:nth-child(5),
#output tr:first-child td:nth-child(6),
#output tr:nth-child(2) td:nth-child(4),
#output tr:nth-child(2) td:nth-child(5),
#output tr:nth-child(2) td:nth-child(6),
#output tr:nth-child(3) td:nth-child(4),
#output tr:nth-child(3) td:nth-child(5),
#output tr:nth-child(3) td:nth-child(6) { cursor: pointer }
<div id="output"></div>
<div id="log"></div>
1
trincot 21 Янв 2021 в 20:50

Каждая грань может быть получена из решаемого куба. Постройте грань, а затем как-нибудь завершите куб. Есть три условия для разрешимости куба: «четность перестановок», «четность ребер» и «четность углов». Если четность перестановки неверна, это можно исправить, поменяв местами два ребра на противоположной грани той, которая вам нужна. Если четность ребер неправильная, это можно исправить, перевернув единственное ребро на противоположной грани. А если четность углов неправильная, один или два скручивания одного угла исправят это.

Это доказательство зависит от того факта, что каждая грань может быть построена, но это легко, поскольку вы выбираете центральный квадрат, а затем выбираете любые края и углы с правильным цветом на них. Нужно немного подумать, чтобы убедить себя, что не может быть недостатка в правильных цветах.

1
Paul Hankin 20 Янв 2021 в 10:54