Самая короткая универсальная строка выхода из лабиринта

48

Лабиринт на сетке N на N квадратных ячеек определяется путем указания того, является ли каждое ребро стеной или нет стеной. Все внешние края - стены. Одна ячейка определяется как начало , а одна ячейка определяется как выход , а выход доступен с самого начала. Начало и выход никогда не являются одной и той же ячейкой.

Обратите внимание, что ни начало, ни выход не должны находиться на внешней границе лабиринта, так что это правильный лабиринт:

Лабиринт 3 на 3 с выходом на центральную клетку

Строка «N», «E», «S» и «W» указывает на попытку перемещения на север, восток, юг и запад соответственно. Ход, заблокированный стеной, пропускается без движения. Строка выходит из лабиринта, если применение этой строки с начала приводит к достижению выхода (независимо от того, продолжается ли строка после достижения выхода).

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

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

Срок действия

Строка должна удовлетворять следующему:

  • Он состоит только из символов «N», «E», «S» и «W».
  • Он выходит из любого лабиринта, к которому он применяется, если он запущен в начале.

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

выигрыш

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

пример

Вот пример строки длиной 500 символов, чтобы дать вам что-то, что можно превзойти:

SEENSSNESSWNNSNNNNWWNWENENNWEENSESSNENSESWENWWWWWENWNWWSESNSWENNWNWENWSSSNNNNNNESWNEWWWWWNNNSWESSEEWNENWENEENNEEESEENSSEENNWWWNWSWNSSENNNWESSESNWESWEENNWSNWWEEWWESNWEEEWWSSSESEEWWNSSEEEEESSENWWNNSWNENSESSNEESENEWSSNWNSEWEEEWEESWSNNNEWNNWNWSSWEESSSSNESESNENNWEESNWEWSWNSNWNNWENSNSWEWSWWNNWNSENESSNENEWNSSWNNEWSESWENEEENSWWSNNNNSSNENEWSNEEWNWENEEWEESEWEEWSSESSSWNWNNSWNWENWNENWNSWESNWSNSSENENNNWSSENSSSWWNENWWWEWSEWSNSSWNNSEWEWENSWENWSENEENSWEWSEWWSESSWWWNWSSEWSNWSNNWESNSNENNSNEWSNNESNNENWNWNNNEWWEWEE

Спасибо orlp за пожертвование.


Leaderboard

Leaderboard

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


судья

Вот валидатор Python 3, который принимает строку NESW в качестве аргумента командной строки или через STDIN.

Для недопустимой строки это даст вам наглядный пример лабиринта, для которого он не подходит.

Trichoplax
источник
3
Это действительно интересный вопрос. Есть ли одна самая короткая строка (или несколько строк и доказательство того, что не может быть более коротких ответов)? И если так, знаете ли вы это?
Алекс Ван Лью
1
@AlexReinking да, начало может быть любой из 9 ячеек, а выходом может быть любая из 9 ячеек, если они не являются одной и той же ячейкой, а выход достижим с самого начала.
трихоплакс
1
Немного похоже на этот вопрос stackoverflow: stackoverflow.com/questions/26910401/… - но начальная и конечная ячейки в этом верхнем левом и нижнем правом углу, что уменьшает возможное количество лабиринтов до 2423.
schnaader
1
@proudhaskeller в любом случае будет правильным вопросом. Общий случай, оцененный для n = 3, потребовал бы более обобщенного кода. Этот конкретный случай учитывает оптимизацию, которая не относится к общему n, и я выбрал именно такой способ.
трихоплакс
2
Кто-нибудь рассматривал решение этой проблемы как поиск кратчайшей принятой строки в регулярном выражении? Для преобразования в регулярные выражения потребовалось бы МНОЖЕСТВО сокращений числа задач, но теоретически можно было бы найти проверяемое оптимальное решение.
Кайл Маккормик,

Ответы:

37

C ++, 97 95 93 91 86 83 82 81 79 символов

NNWSWNNSENESESWSSWNSEENWWNWSSEWWNENWEENWSWNWSSENENWNWNESENESESWNWSESEWWNENWNEES

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

Однако моя реализация логики лабиринта довольно изящна. Это позволяет мне проверить, действительны ли строки на скорости волдырей. Попробуйте выяснить это, посмотрев на комментарий do_moveи Mazeконструктор.

#include <algorithm>
#include <bitset>
#include <cstdint>
#include <iostream>
#include <random>
#include <set>
#include <vector>

/*
    Positions:

        8, 10, 12
        16, 18, 20
        24, 26, 28

    By defining as enum respectively N, W, E, S as 0, 1, 2, 3 we get:

        N: -8, E: 2, S: 8, W: -2
        0: -8, 1: -2, 2: 2, 3: 8

    To get the indices for the walls, average the numbers of the positions it
    would be blocking. This gives the following indices:

        9, 11, 12, 14, 16, 17, 19, 20, 22, 24, 25, 27

    We'll construct a wall mask with a 1 bit for every position that does not
    have a wall. Then if a 1 shifted by the average of the positions AND'd with
    the wall mask is zero, we have hit a wall.
*/

enum { N = -8, W = -2, E = 2, S = 8 };
static const int encoded_pos[] = {8, 10, 12, 16, 18, 20, 24, 26, 28};
static const int wall_idx[] = {9, 11, 12, 14, 16, 17, 19, 20, 22, 24, 25, 27};
static const int move_offsets[] = { N, W, E, S };

int do_move(uint32_t walls, int pos, int move) {
    int idx = pos + move / 2;
    return walls & (1ull << idx) ? pos + move : pos;
}

struct Maze {
    uint32_t walls;
    int start, end;

    Maze(uint32_t maze_id, int start, int end) {
        walls = 0;
        for (int i = 0; i < 12; ++i) {
            if (maze_id & (1 << i)) walls |= 1 << wall_idx[i];
        }
        this->start = encoded_pos[start];
        this->end = encoded_pos[end];
    }

    uint32_t reachable() {
        if (start == end) return false;

        uint32_t reached = 0;
        std::vector<int> fill; fill.reserve(8); fill.push_back(start);
        while (fill.size()) {
            int pos = fill.back(); fill.pop_back();
            if (reached & (1 << pos)) continue;
            reached |= 1 << pos;
            for (int m : move_offsets) fill.push_back(do_move(walls, pos, m));
        }

        return reached;
    }

    bool interesting() {
        uint32_t reached = reachable();
        if (!(reached & (1 << end))) return false;
        if (std::bitset<32>(reached).count() <= 4) return false;

        int max_deg = 0;
        uint32_t ends = 0;
        for (int p = 0; p < 9; ++p) {
            int pos = encoded_pos[p];
            if (reached & (1 << pos)) {
                int deg = 0;
                for (int m : move_offsets) {
                    if (pos != do_move(walls, pos, m)) ++deg;
                }
                if (deg == 1) ends |= 1 << pos;
                max_deg = std::max(deg, max_deg);
            }
        }

        if (max_deg <= 2 && ends != ((1u << start) | (1u << end))) return false;

        return true;
    }
};

std::vector<Maze> gen_valid_mazes() {
    std::vector<Maze> mazes;
    for (int maze_id = 0; maze_id < (1 << 12); maze_id++) {
        for (int points = 0; points < 9*9; ++points) {
            Maze maze(maze_id, points % 9, points / 9);
            if (!maze.interesting()) continue;
            mazes.push_back(maze);
        }
    }

    return mazes;
}

bool is_solution(const std::vector<int>& moves, Maze maze) {
    int pos = maze.start;
    for (auto move : moves) {
        pos = do_move(maze.walls, pos, move);
        if (pos == maze.end) return true;
    }

    return false;
}

std::vector<int> str_to_moves(std::string str) {
    std::vector<int> moves;
    for (auto c : str) {
        switch (c) {
        case 'N': moves.push_back(N); break;
        case 'E': moves.push_back(E); break;
        case 'S': moves.push_back(S); break;
        case 'W': moves.push_back(W); break;
        }
    }

    return moves;
}

std::string moves_to_str(const std::vector<int>& moves) {
    std::string result;
    for (auto move : moves) {
             if (move == N) result += "N";
        else if (move == E) result += "E";
        else if (move == S) result += "S";
        else if (move == W) result += "W";
    }
    return result;
}

bool solves_all(const std::vector<int>& moves, std::vector<Maze>& mazes) {
    for (size_t i = 0; i < mazes.size(); ++i) {
        if (!is_solution(moves, mazes[i])) {
            // Bring failing maze closer to begin.
            std::swap(mazes[i], mazes[i / 2]);
            return false;
        }
    }
    return true;
}

template<class Gen>
int randint(int lo, int hi, Gen& gen) {
    return std::uniform_int_distribution<int>(lo, hi)(gen);
}

template<class Gen>
int randmove(Gen& gen) { return move_offsets[randint(0, 3, gen)]; }

constexpr double mutation_p = 0.35; // Chance to mutate.
constexpr double grow_p = 0.1; // Chance to grow.
constexpr double swap_p = 0.2; // Chance to swap.

int main(int argc, char** argv) {
    std::random_device rnd;
    std::mt19937 rng(rnd());
    std::uniform_real_distribution<double> real;
    std::exponential_distribution<double> exp_big(0.5);
    std::exponential_distribution<double> exp_small(2);

    std::vector<Maze> mazes = gen_valid_mazes();

    std::vector<int> moves;
    while (!solves_all(moves, mazes)) {
        moves.clear();
        for (int m = 0; m < 500; m++) moves.push_back(randmove(rng));
    }

    size_t best_seen = moves.size();
    std::set<std::vector<int>> printed;
    while (true) {
        std::vector<int> new_moves(moves);
        double p = real(rng);

        if (p < grow_p && moves.size() < best_seen + 10) {
            int idx = randint(0, new_moves.size() - 1, rng);
            new_moves.insert(new_moves.begin() + idx, randmove(rng));
        } else if (p < swap_p) {
            int num_swap = std::min<int>(1 + exp_big(rng), new_moves.size()/2);
            for (int i = 0; i < num_swap; ++i) {
                int a = randint(0, new_moves.size() - 1, rng);
                int b = randint(0, new_moves.size() - 1, rng);
                std::swap(new_moves[a], new_moves[b]);
            }
        } else if (p < mutation_p) {
            int num_mut = std::min<int>(1 + exp_big(rng), new_moves.size());
            for (int i = 0; i < num_mut; ++i) {
                int idx = randint(0, new_moves.size() - 1, rng);
                new_moves[idx] = randmove(rng);
            }
        } else {
            int num_shrink = std::min<int>(1 + exp_small(rng), new_moves.size());
            for (int i = 0; i < num_shrink; ++i) {
                int idx = randint(0, new_moves.size() - 1, rng);
                new_moves.erase(new_moves.begin() + idx);
            }
        }

        if (solves_all(new_moves, mazes)) {
            moves = new_moves;

            if (moves.size() <= best_seen && !printed.count(moves)) {
                std::cout << moves.size() << " " << moves_to_str(moves) << "\n";
                if (moves.size() < best_seen) {
                    printed.clear(); best_seen = moves.size();
                }
                printed.insert(moves);
            }
        }
    }

    return 0;
}
orlp
источник
5
Подтверждено действительным. Я впечатлен - я не ожидал увидеть такие короткие строки.
Трихоплакс
2
Я наконец дошел до установки gcc и запуска этого для себя. Гипнотично наблюдать, как струны мутируют и медленно сжимаются ...
trichoplax
1
@trichoplax Я сказал тебе, что это было весело :)
orlp
2
@AlexReinking Я обновил свой ответ с указанной реализацией. Если вы посмотрите на разборку, то увидите, что это всего лишь дюжина инструкций без какой-либо ветки или загрузки: coliru.stacked-crooked.com/a/3b09d36db85ce793 .
orlp
2
@AlexReinking Готово. do_moveсейчас безумно быстро.
orlp
16

Python 3 + PyPy, 82 80 символов

SWWNNSENESESWSSWSEENWNWSWSEWNWNENENWWSESSEWSWNWSENWEENWWNNESENESSWNWSESESWWNNESE

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

Единственная новая важная оптимизация заключается в том, что я смотрю только на одну треть лабиринтов. Две категории лабиринтов исключены из поиска:

  • Лабиринты, где <= 7квадратики достижимы
  • Лабиринты, где все достижимые квадраты находятся на одной дорожке, а начало / конец не на обоих концах

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

Сначала я прошел длинный список эвристик поиска, но никому из них не удалось придумать ничего лучше, чем 140 или около того.

import random

N, M = 3, 3

W = 2*N-1
H = 2*M-1

random.seed(142857)


def move(c, cell, walls):
    global W, H

    if c == "N":
        if cell > W and not (1<<(cell-W)//2 & walls):
            cell = cell - W*2

    elif c == "S":
        if cell < W*(H-1) and not (1<<(cell+W)//2 & walls):
            cell = cell + W*2

    elif c == "E":
        if cell % W < W-1 and not (1<<(cell+1)//2 & walls):
            cell = cell + 2

    elif c == "W":
        if cell % W > 0 and not (1<<(cell-1)//2 & walls):
            cell = cell - 2

    return cell


def valid_maze(start, finish, walls):
    global adjacent

    if start == finish:
        return False

    visited = set()
    cells = [start]

    while cells:
        curr_cell = cells.pop()

        if curr_cell == finish:
            return True

        if curr_cell in visited:
            continue

        visited.add(curr_cell)

        for c in "NSEW":
            cells.append(move(c, curr_cell, walls))

    return False


def print_maze(maze):
    start, finish, walls = maze
    print_str = "".join(" #"[walls & (1 << i//2) != 0] if i%2 == 1
                        else " SF"[2*(i==finish) + (i==start)]
                        for i in range(W*H))

    print("#"*(H+2))

    for i in range(H):
        print("#" + print_str[i*W:(i+1)*W] + "#")

    print("#"*(H+2), end="\n\n")

all_cells = [W*y+x for y in range(0, H, 2) for x in range(0, W, 2)]
mazes = []

for start in all_cells:
    for finish in all_cells:
        for walls in range(1<<(N*(M-1) + M*(N-1))):
            if valid_maze(start, finish, walls):
                mazes.append((start, finish, walls))

num_mazes = len(mazes)
print(num_mazes, "mazes generated")

to_remove = set()

for i, maze in enumerate(mazes):
    start, finish, walls = maze

    reachable = set()
    cells = [start]

    while cells:
        cell = cells.pop()

        if cell in reachable:
            continue

        reachable.add(cell)

        if cell == finish:
            continue

        for c in "NSEW":
            new_cell = move(c, cell, walls)
            cells.append(new_cell)

    max_deg = 0
    sf = set()

    for cell in reachable:
        deg = 0

        for c in "NSEW":
            if move(c, cell, walls) != cell:
                deg += 1

        max_deg = max(deg, max_deg)

        if deg == 1:
            sf.add(cell)

    if max_deg <= 2 and len(sf) == 2 and sf != {start, finish}:
        # Single path subset
        to_remove.add(i)

    elif len(reachable) <= (N*M*4)//5:
        # Low reachability maze, above ratio is adjustable
        to_remove.add(i)

mazes = [maze for i,maze in enumerate(mazes) if i not in to_remove]
print(num_mazes - len(mazes), "mazes removed,", len(mazes), "remaining")
num_mazes = len(mazes)


def check(string, cache = set()):
    global mazes

    if string in cache:
        return True

    for i, maze in enumerate(mazes):
        start, finish, walls = maze
        cell = start

        for c in string:
            cell = move(c, cell, walls)

            if cell == finish:
                break

        else:
            # Swap maze to front
            mazes[i//2], mazes[i] = mazes[i], mazes[i//2]
            return False

    cache.add(string)
    return True


while True:
    string = "".join(random.choice("NSEW") for _ in range(500))

    if check(string):
        break

# string = "NWWSSESNESESNNWNNSWNWSSENESWSWNENENWNWESESENNESWSESWNWSWNNEWSESWSEEWNENWWSSNNEESS"

best = len(string)
seen = set()

while True:
    action = random.random()

    if action < 0.1:
        # Grow
        num_grow = int(random.expovariate(lambd=3)) + 1
        new_string = string

        for _ in range(num_grow):
            i = random.randrange(len(new_string))
            new_string = new_string[:i] + random.choice("NSEW") + new_string[i:]

    elif action < 0.2:
        # Swap
        num_swap = int(random.expovariate(lambd=1)) + 1
        new_string = string

        for _ in range(num_swap):
            i,j = sorted(random.sample(range(len(new_string)), 2))
            new_string = new_string[:i] + new_string[j] + new_string[i+1:j] + new_string[i] + new_string[j+1:]

    elif action < 0.35:
        # Mutate
        num_mutate = int(random.expovariate(lambd=1)) + 1
        new_string = string

        for _ in range(num_mutate):
            i = random.randrange(len(new_string))
            new_string = new_string[:i] + random.choice("NSEW") + new_string[i+1:]

    else:
        # Shrink
        num_shrink = int(random.expovariate(lambd=3)) + 1
        new_string = string

        for _ in range(num_shrink):
            i = random.randrange(len(new_string))
            new_string = new_string[:i] + new_string[i+1:]


    if check(new_string):
        string = new_string

    if len(string) <= best and string not in seen:
        while True:
            if len(string) < best:
                seen = set()

            seen.add(string)
            best = len(string)
            print(string, len(string))

            # Force removals on new record strings
            for i in range(len(string)):
                new_string = string[:i] + string[i+1:]

                if check(new_string):
                    string = new_string
                    break

            else:
                break
Sp3000
источник
Подтверждено действительным. Хорошие улучшения :)
trichoplax
Мне нравится ваша идея реализации некоторых лабиринтов, не нужно проверять. Не могли бы вы как-нибудь автоматизировать процесс определения, какие лабиринты являются избыточными проверками? Мне любопытно узнать, покажет ли это больше лабиринтов, чем те, которые могут быть выведены интуитивно ...
trichoplax
Почему вы не нуждаетесь в проверке графов путей, где начало не на одном конце? Случай, когда финиш не находится на одном конце, легко обосновать, и его можно усилить, чтобы не нужно было проверять случаи, когда финиш является вырезанной вершиной, но я не понимаю, как оправдать устранение начальных вершин.
Питер Тейлор
@PeterTaylor После долгих размышлений, теоретически вы правы, есть лабиринты, которые вы не можете устранить таким образом. Тем не менее, кажется, что на 3х3 это не имеет значения для длинных строк.
orlp
2
@orlp, Sp3000 набросал доказательство в чате. Графы путей - это особый случай. Обозначить клетки , 0чтобы nпо пути , и предположим , что строка Sполучает вас от 0к n. Затем Sтакже доставит вас из любой промежуточной клетки cв n. Предположим иначе. Позвольте a(i)быть позиции после iшагов, начиная с 0и b(i)начиная с c. Затем a(0) = 0 < b(0)каждый шаг изменяется aи не bболее чем на 1, и a(|S|) = n > b(|S|). Возьми самый маленький tтакой, что a(t) >= b(t). Ясно, a(t) != b(t)иначе они будут синхронизированы, поэтому они должны поменяться местами, шагая tв одном направлении.
Питер Тейлор
3

C ++ и библиотека от lingeling

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

Используя подход, основанный на SAT , я мог бы полностью решить аналогичную проблему для лабиринтов 4x4 с заблокированными ячейками вместо тонких стен и фиксированными начальными и выходными положениями в противоположных углах. Поэтому я надеялся, что смогу использовать те же идеи для этой проблемы. Однако, хотя для другой проблемы я использовал только 2423 лабиринта (в то время как было замечено, что 2083 достаточно), и он имеет решение длины 29, в кодировании SAT использовались миллионы переменных, и для его решения потребовались дни.

Поэтому я решил изменить подход двумя важными способами:

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

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

Программа основана на @ orlp. Важным изменением стал выбор лабиринтов:

  • Прежде всего, лабиринты определяются только структурой стен и начальной позицией. (Они также хранят достижимые позиции.) Функция is_solutionпроверяет, все ли достижимые позиции достигнуты.
  • (Без изменений: по-прежнему не используются лабиринты только с 4 или менее доступными позициями. Но большинство из них все равно будут выброшены следующими наблюдениями.)
  • Если в лабиринте не используется ни одна из трех верхних ячеек, это эквивалентно смещенному вверх лабиринту. Таким образом, мы можем бросить это. Аналогично для лабиринта, который не использует ни одну из трех левых клеток.
  • Не имеет значения, связаны ли недоступные части, поэтому мы настаиваем на том, чтобы каждая недоступная ячейка была полностью окружена стенами.
  • Лабиринт с одним путем, который является субмазой большего лабиринта с одним путем, всегда решается, когда решается больший лабиринт, поэтому он нам не нужен. Каждый отдельный лабиринт размером не более 7 является частью большего (все еще умещающегося в 3х3), но есть лабиринты 8-го размера, которых нет. Для простоты, давайте просто отбросим одиночные лабиринты размером менее 8. (И я все еще использую то, что только крайние точки должны рассматриваться как начальные позиции. Все позиции используются в качестве выходных позиций, что имеет значение только для части SAT программы.)

Таким образом, я получаю в общей сложности 10772 лабиринта со стартовыми позициями.

Вот программа:

#include <algorithm>
#include <array>
#include <bitset>
#include <cstring>
#include <iostream>
#include <set>
#include <vector>
#include <limits>
#include <cassert>

extern "C"{
#include "lglib.h"
}

// reusing a lot of @orlp's ideas and code

enum { N = -8, W = -2, E = 2, S = 8 };
static const int encoded_pos[] = {8, 10, 12, 16, 18, 20, 24, 26, 28};
static const int wall_idx[] = {9, 11, 12, 14, 16, 17, 19, 20, 22, 24, 25, 27};
static const int move_offsets[] = { N, E, S, W };
static const uint32_t toppos = 1ull << 8 | 1ull << 10 | 1ull << 12;
static const uint32_t leftpos = 1ull << 8 | 1ull << 16 | 1ull << 24;
static const int unencoded_pos[] = {0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,3,
                                    0,4,0,5,0,0,0,6,0,7,0,8};

int do_move(uint32_t walls, int pos, int move) {
  int idx = pos + move / 2;
  return walls & (1ull << idx) ? pos + move : pos;
}

struct Maze {
  uint32_t walls, reach;
  int start;

  Maze(uint32_t walls=0, uint32_t reach=0, int start=0):
    walls(walls),reach(reach),start(start) {}

  bool is_dummy() const {
    return (walls==0);
  }

  std::size_t size() const{
    return std::bitset<32>(reach).count();
  }

  std::size_t simplicity() const{  // how many potential walls aren't there?
    return std::bitset<32>(walls).count();
  }

};

bool cmp(const Maze& a, const Maze& b){
  auto asz = a.size();
  auto bsz = b.size();
  if (asz>bsz) return true;
  if (asz<bsz) return false;
  return a.simplicity()<b.simplicity();
}

uint32_t reachable(uint32_t walls) {
  static int fill[9];
  uint32_t reached = 0;
  uint32_t reached_relevant = 0;
  for (int start : encoded_pos){
    if ((1ull << start) & reached) continue;
    uint32_t reached_component = (1ull << start);
    fill[0]=start;
    int count=1;
    for(int i=0; i<count; ++i)
      for(int m : move_offsets) {
        int newpos = do_move(walls, fill[i], m);
        if (reached_component & (1ull << newpos)) continue;
        reached_component |= 1ull << newpos;
        fill[count++] = newpos;
      }
    if (count>1){
      if (reached_relevant)
        return 0;  // more than one nonsingular component
      if (!(reached_component & toppos) || !(reached_component & leftpos))
        return 0;  // equivalent to shifted version
      if (std::bitset<32>(reached_component).count() <= 4)
        return 0;  
      reached_relevant = reached_component;
    }
    reached |= reached_component;
  }
  return reached_relevant;
}

void enterMazes(uint32_t walls, uint32_t reached, std::vector<Maze>& mazes){
  int max_deg = 0;
  uint32_t ends = 0;
  for (int pos : encoded_pos)
    if (reached & (1ull << pos)) {
      int deg = 0;
      for (int m : move_offsets) {
        if (pos != do_move(walls, pos, m))
          ++deg;
      }
      if (deg == 1)
        ends |= 1ull << pos;
      max_deg = std::max(deg, max_deg);
    }
  uint32_t starts = reached;
  if (max_deg == 2){
    if (std::bitset<32>(reached).count() <= 7)
      return; // small paths are redundant
    starts = ends; // need only start at extremal points
  }
  for (int pos : encoded_pos)
    if ( starts & (1ull << pos))
      mazes.emplace_back(walls, reached, pos);
}

std::vector<Maze> gen_valid_mazes() {
  std::vector<Maze> mazes;
  for (int maze_id = 0; maze_id < (1 << 12); maze_id++) {
    uint32_t walls = 0;
    for (int i = 0; i < 12; ++i) 
      if (maze_id & (1 << i))
    walls |= 1ull << wall_idx[i];
    uint32_t reached=reachable(walls);
    if (!reached) continue;
    enterMazes(walls, reached, mazes);
  }
  std::sort(mazes.begin(),mazes.end(),cmp);
  return mazes;
};

bool is_solution(const std::vector<int>& moves, Maze& maze) {
  int pos = maze.start;
  uint32_t reached = 1ull << pos;
  for (auto move : moves) {
    pos = do_move(maze.walls, pos, move);
    reached |= 1ull << pos;
    if (reached == maze.reach) return true;
  }
  return false;
}

std::vector<int> str_to_moves(std::string str) {
  std::vector<int> moves;
  for (auto c : str) {
    switch (c) {
    case 'N': moves.push_back(N); break;
    case 'E': moves.push_back(E); break;
    case 'S': moves.push_back(S); break;
    case 'W': moves.push_back(W); break;
    }
  }
  return moves;
}

Maze unsolved(const std::vector<int>& moves, std::vector<Maze>& mazes) {
  int unsolved_count = 0;
  Maze problem{};
  for (Maze m : mazes)
    if (!is_solution(moves, m))
      if(!(unsolved_count++))
    problem=m;
  if (unsolved_count)
    std::cout << "unsolved: " << unsolved_count << "\n";
  return problem;
}

LGL * lgl;

constexpr int TRUELIT = std::numeric_limits<int>::max();
constexpr int FALSELIT = -TRUELIT;

int new_var(){
  static int next_var = 1;
  assert(next_var<TRUELIT);
  return next_var++;
}

bool lit_is_true(int lit){
  int abslit = lit>0 ? lit : -lit;
  bool res = (abslit==TRUELIT) || (lglderef(lgl,abslit)>0);
  return lit>0 ? res : !res;
}

void unsat(){
  std::cout << "Unsatisfiable!\n";
  std::exit(1);
}

void clause(const std::set<int>& lits){
  if (lits.find(TRUELIT) != lits.end())
    return;
  for (int lit : lits)
    if (lits.find(-lit) != lits.end())
      return;
  int found=0;
  for (int lit : lits)
    if (lit != FALSELIT){
      lgladd(lgl, lit);
      found=1;
    }
  lgladd(lgl, 0);
  if (!found)
    unsat();
}

void at_most_one(const std::set<int>& lits){
  if (lits.size()<2)
    return;
  for(auto it1=lits.cbegin(); it1!=lits.cend(); ++it1){
    auto it2=it1;
    ++it2;
    for(  ; it2!=lits.cend(); ++it2)
      clause( {- *it1, - *it2} );
  }
}

/* Usually, lit_op(lits,sgn) creates a new variable which it returns,
   and adds clauses that ensure that the variable is equivalent to the
   disjunction (if sgn==1) or the conjunction (if sgn==-1) of the literals
   in lits. However, if this disjunction or conjunction is constant True
   or False or simplifies to a single literal, that is returned without
   creating a new variable and without adding clauses.                    */ 

int lit_op(std::set<int> lits, int sgn){
  if (lits.find(sgn*TRUELIT) != lits.end())
    return sgn*TRUELIT;
  lits.erase(sgn*FALSELIT);
  if (!lits.size())
    return sgn*FALSELIT;
  if (lits.size()==1)
    return *lits.begin();
  int res=new_var();
  for(int lit : lits)
    clause({sgn*res,-sgn*lit});
  for(int lit : lits)
    lgladd(lgl,sgn*lit);
  lgladd(lgl,-sgn*res);
  lgladd(lgl,0);
  return res;
}

int lit_or(std::set<int> lits){
  return lit_op(lits,1);
}

int lit_and(std::set<int> lits){
  return lit_op(lits,-1);
}

using A4 = std::array<int,4>;

void add_maze_conditions(Maze m, std::vector<A4> dirs, int len){
  int mp[9][2];
  int rp[9];
  for(int p=0; p<9; ++p)
    if((1ull << encoded_pos[p]) & m.reach)
      rp[p] = mp[p][0] = encoded_pos[p]==m.start ? TRUELIT : FALSELIT;
  int t=0;
  for(int i=0; i<len; ++i){
    std::set<int> posn {};
    for(int p=0; p<9; ++p){
      int ep = encoded_pos[p];
      if((1ull << ep) & m.reach){
        std::set<int> reach_pos {};
        for(int d=0; d<4; ++d){
          int np = do_move(m.walls, ep, move_offsets[d]);
          reach_pos.insert( lit_and({mp[unencoded_pos[np]][t],
                                  dirs[i][d ^ ((np==ep)?0:2)]    }));
        }
        int pl = lit_or(reach_pos);
        mp[p][!t] = pl;
        rp[p] = lit_or({rp[p], pl});
        posn.insert(pl);
      }
    }
    at_most_one(posn);
    t=!t;
  }
  for(int p=0; p<9; ++p)
    if((1ull << encoded_pos[p]) & m.reach)
      clause({rp[p]});
}

void usage(char* argv0){
  std::cout << "usage: " << argv0 <<
    " <string>\n   where <string> consists of 'N', 'E', 'S', 'W' and '*'.\n" ;
  std::exit(2);
}

const std::string nesw{"NESW"};

int main(int argc, char** argv) {
  if (argc!=2)
    usage(argv[0]);
  std::vector<Maze> mazes = gen_valid_mazes();
  std::cout << "Mazes with start positions: " << mazes.size() << "\n" ;
  lgl = lglinit();
  int len = std::strlen(argv[1]);
  std::cout << argv[1] << "\n   with length " << len << "\n";

  std::vector<A4> dirs;
  for(int i=0; i<len; ++i){
    switch(argv[1][i]){
    case 'N':
      dirs.emplace_back(A4{TRUELIT,FALSELIT,FALSELIT,FALSELIT});
      break;
    case 'E':
      dirs.emplace_back(A4{FALSELIT,TRUELIT,FALSELIT,FALSELIT});
      break;
    case 'S':
      dirs.emplace_back(A4{FALSELIT,FALSELIT,TRUELIT,FALSELIT});
      break;
    case 'W':
      dirs.emplace_back(A4{FALSELIT,FALSELIT,FALSELIT,TRUELIT});
      break;
    case '*': {
      dirs.emplace_back();
      std::generate_n(dirs[i].begin(),4,new_var);
      std::set<int> dirs_here { dirs[i].begin(), dirs[i].end() };
      at_most_one(dirs_here);
      clause(dirs_here);
      for(int l : dirs_here)
        lglfreeze(lgl,l);
      break;
      }
    default:
      usage(argv[0]);
    }
  }

  int maze_nr=0;
  for(;;) {
    std::cout << "Solving...\n";
    int res=lglsat(lgl);
    if(res==LGL_UNSATISFIABLE)
      unsat();
    assert(res==LGL_SATISFIABLE);
    std::string sol(len,' ');
    for(int i=0; i<len; ++i)
      for(int d=0; d<4; ++d)
        if (lit_is_true(dirs[i][d])){
          sol[i]=nesw[d];
          break;
    }
    std::cout << sol << "\n";

    Maze m=unsolved(str_to_moves(sol),mazes);
    if (m.is_dummy()){
      std::cout << "That solves all!\n";
      return 0;
    }
    std::cout << "Adding maze " << ++maze_nr << ": " << 
      m.walls << "/" << m.start <<
      " (" << m.size() << "/" << 12-m.simplicity() << ")\n";
    add_maze_conditions(m,dirs,len);
  }
}  

Во- первых , configure.shи решателя, а затем скомпилировать программу с чем - то вроде , где есть путь , где соответственно. есть, так что оба могут, например, быть . Или просто поместите их в тот же каталог и обойтись без параметров и .makelingelingg++ -std=c++11 -O3 -I ... -o m3sat m3sat.cc -L ... -llgl...lglib.hliblgl.a../lingeling-<version>-I-L

Программа принимает один обязательный аргумент командной строки, строка , состоящая из N, E, S, W(для фиксированных направлений) или *. Таким образом, вы можете найти общее решение размера 78, указав строку 78 *с (в кавычках), или найти решение, начинающееся с NEWS, NEWSпосле чего укажите столько *s, сколько вы хотите для дополнительных шагов. В качестве первого теста возьмите ваше любимое решение и замените некоторые буквы на *. Это быстро находит решение для удивительно высокого значения «некоторые».

Программа скажет, какой лабиринт он добавляет, описывается структурой стены и начальным положением, а также даст количество доступных мест и стен. Лабиринты сортируются по этим критериям, и добавляется первый нерешенный. Поэтому большинство добавленных лабиринтов есть (9/4), но иногда появляются и другие.

Я взял известное решение длиной 79 и для каждой группы из 26 смежных букв попытался заменить их любыми 25 буквами. Я также попытался удалить 13 букв из начала и конца и заменить их на любые 13 в начале и любые 12 в конце, и наоборот. К сожалению, все вышло неудовлетворительно. Итак, можем ли мы принять это как показатель того, что длина 79 является оптимальной? Нет, я также попытался улучшить решение длины 80 до длины 79, и это также не увенчалось успехом.

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

Кристиан Сиверс
источник
Это было действительно интересное чтение. И новый подход, и различные способы уменьшения количества лабиринтов для проверки. Чтобы быть верным ответом, для этого нужно будет указать правильную строку. Это не должна быть новая самая короткая строка, просто любая допустимая строка любой длины, чтобы дать текущий счет для этого подхода. Я упоминаю об этом, потому что без оценки ответ может быть удален, и я бы очень хотел, чтобы он остался.
трихоплакс
Также хорошая работа по поиску решения оптимальной длины для старшей связанной задачи !
трихоплакс