가장 짧은 범용 미로 출구 문자열


48

정사각형 셀의 N x N 격자에있는 미로는 각 모서리가 벽인지 아닌지를 지정하여 정의됩니다. 모든 외부 모서리는 벽입니다. 하나의 셀은 시작 으로 정의 되고 하나의 셀은 종료 로 정의되며 종료는 시작부터 도달 할 수 있습니다. 시작과 종료는 같은 셀이 아닙니다.

시작과 출구가 미로의 바깥 경계에있을 필요는 없으므로 유효한 미로입니다.

중앙 셀에 출구가있는 3 x 3 미로

'N', 'E', 'S'및 'W'문자열은 각각 북쪽, 동쪽, 남쪽 및 서쪽으로 이동하려고 시도 함을 나타냅니다. 벽으로 막힌 이동은 이동하지 않고 건너 뜁니다. 문자열 종료 출구에서 시작 결과에서 해당 문자열을 적용하는 것은 (관계없이 문자열이 출구에 도달 한 후 계속 여부)에 도달되는 경우 미로.

영감을받은 이 puzzling.SE 질문 하는 XNOR가 제공된 해결의 증명 방법 으로 매우 긴 문자열을, 3 미로에 의해 모든 3를 종료 한 문자열을 찾을 수있는 코드를 작성합니다.

유효하지 않은 미로 (동일한 셀에서 시작 및 종료 또는 시작에서 도달 할 수없는 종료)를 제외하면 138,172 개의 유효한 미로가 있으며 문자열은 각각을 종료해야합니다.

효력

문자열은 다음을 충족해야합니다.

  • 문자 'N', 'E', 'S'및 'W'만으로 구성됩니다.
  • 시작시 시작되면 적용된 미로를 종료합니다.

모든 가능한 미로들의 세트는 각각의 가능한 유효 시작점 각 가능한 미로를 포함하기 때문에, 본 자동 문자열 어떠한 미로 종료된다는 것을 의미 어떤 유효한 시작점. 즉, 출구에 도달 할 수있는 시작 지점부터입니다.

승리

승자는 가장 짧은 유효한 문자열을 제공하고이를 생성하는 데 사용되는 코드를 포함하는 답변입니다. 둘 이상의 답변이이 최단 길이의 문자열을 제공하는 경우 해당 문자열 길이를 처음 게시 한 것이 우선합니다.

다음은 이길 수있는 500 자 길이의 문자열 예입니다.

SEENSSNESSWNNSNNNNWWNWENENNWEENSESSNENSESWENWWWWWENWNWWSESNSWENNWNWENWSSSNNNNNNESWNEWWWWWNNNSWESSEEWNENWENEENNEEESEENSSEENNWWWNWSWNSSENNNWESSESNWESWEENNWSNWWEEWWESNWEEEWWSSSESEEWWNSSEEEEESSENWWNNSWNENSESSNEESENEWSSNWNSEWEEEWEESWSNNNEWNNWNWSSWEESSSSNESESNENNWEESNWEWSWNSNWNNWENSNSWEWSWWNNWNSENESSNENEWNSSWNNEWSESWENEEENSWWSNNNNSSNENEWSNEEWNWENEEWEESEWEEWSSESSSWNWNNSWNWENWNENWNSWESNWSNSSENENNNWSSENSSSWWNENWWWEWSEWSNSSWNNSEWEWENSWENWSENEENSWEWSEWWSESSWWWNWSSEWSNWSNNWESNSNENNSNEWSNNESNNENWNWNNNEWWEWEE

감사합니다 orlp 이 기부에 대해.


리더 보드

리더 보드

동일한 점수는 해당 점수를 게시 한 순서대로 나열됩니다. 주어진 답변에 대한 점수가 시간이 지남에 따라 업데이트 될 수 있으므로 반드시 답변이 게시 된 순서는 아닙니다.


판사

다음은 NESW 문자열을 명령 행 인수 또는 STDIN을 통해 사용 하는 Python 3 유효성 검증기 입니다.

잘못된 문자열의 경우, 실패한 미로의 시각적 예를 제공합니다.


3
이것은 정말 깔끔한 질문입니다. 하나의 가장 짧은 문자열 (또는 여러 개의 문자열과 더 짧은 답변이 없다는 증거)이 있습니까? 그렇다면, 알고 있습니까?
Alex Van Liew

1
@AlexReinking 예 시작은 9 셀 중 하나 일 수 있으며 종료는 동일한 셀이 아닌 한 9 셀 중 하나 일 수 있으며 종료는 시작부터 도달 할 수 있습니다.
trichoplax

1
이 stackoverflow 질문과 약간 유사합니다 : stackoverflow.com/questions/26910401/…- 시작 및 종료 셀은 왼쪽 상단과 오른쪽 하단에 있으며, 이로 인해 미로 수가 2423으로 줄어 듭니다.
schnaader

1
@proudhaskeller 어느 쪽이든 유효한 질문입니다. n = 3으로 기록 된 일반적인 경우에는보다 일반화 된 코드가 필요합니다. 이 특정 사례는 일반 n에 적용되지 않는 최적화를 허용하며 이것이 내가 요청한 방식입니다.
trichoplax

2
누구 든지이 문제에 접근하는 것이 정규식에 가장 짧은 허용 문자열을 찾는 것으로 생각 했습니까? 정규식으로 변환하기 전에 많은 수의 문제를 줄여야하지만 이론적으로는 최적의 솔루션을 찾을 수 있습니다.
Kyle McCormick

답변:


37

C ++, 97 95 93 91 86 83 82 81 79 자

NNWSWNNSENESESWSSWNSEENWWNWSSEWNENWEENWSWNWSSENENWNWNESENESESWNWSESEWWNENWNEES

내 전략은 매우 간단합니다. 유효한 시퀀스의 요소를 확장, 축소, 교체 및 변형 할 수있는 진화 알고리즘입니다. 내 진화 논리는 이제 @ Sp3000과 거의 동일합니다.

그러나 미로 논리의 구현은 다소 훌륭합니다. 이렇게하면 문자열이 물집 속도로 유효한지 확인할 수 있습니다. 주석 do_moveMaze생성자 를보고 파악하십시오 .

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

5
유효한 것으로 확인되었습니다. 나는 감동한다-나는이 짧은 줄을 기대하지 않았다.
trichoplax

2
마침내 gcc를 설치하고 직접 실행했습니다. 그것은 줄이
변하고

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의 접근 방식을 취하고 그것에 내 자신의 스핀을 넣었 기 때문에이 답변을 게시하는 것을 주저했습니다. 이 문자열은 pseudorandom length 500 솔루션으로 시작하여 발견되었습니다. 현재 레코드를 깨기 전에 상당히 많은 씨앗이 시도되었습니다.

유일한 새로운 주요 최적화는 미로의 3 분의 1 만 보는 것입니다. 검색에서 두 가지 범주의 미로가 제외됩니다.

  • <= 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

유효한 것으로 확인되었습니다. 좋은 개선 :)
trichoplax

미로를 확인하지 않아도된다는 생각이 마음에 듭니다. 중복 검사가 어떤 미로인지를 결정하는 프로세스를 어떻게 자동화 할 수 있습니까? 직관적으로 추론 할 수있는 것보다 더 많은 미로가
나타날지 궁금합니다

시작이 끝나지 않은 경로 그래프를 확인할 필요가없는 이유는 무엇입니까? 마무리가 한쪽 끝에 있지 않은 경우는 정당화하기 쉽고 마무리가 정점 인 경우를 확인할 필요가 없어 강화 될 수 있지만 시작 꼭지점 제거를 정당화하는 방법을 볼 수는 없습니다.
피터 테일러

@PeterTaylor 더 많은 생각을 한 후에 이론적으로는 옳습니다. 그렇게 제거 할 수없는 미로가 있습니다. 그러나 3x3에서는 문자열이 오래 걸리지 않습니다.
orlp

2
@orlp, Sp3000은 채팅에서 증거를 스케치했습니다. 경로 그래프는 특별한 경우입니다. 경로 0n따라 셀의 번호를 다시 매기고 문자열 S이에서 0로 이동 한다고 가정하십시오 n. 그런 다음 S중간 셀에서 c로 이동 합니다 n. 그렇지 않다고 가정하십시오. 하자 a(i)후 위치 할 i단계에서 시작 0하고 b(i)부터 c. 이어서 a(0) = 0 < b(0), 각각의 단계 변화 ab최대 1 및 의해 a(|S|) = n > b(|S|). t그런 가장 작은 것을 가져 가라 a(t) >= b(t). 분명히 a(t) != b(t)또는 동기화 상태이므로 t동일한 방향으로 이동 하여 단계적 으로 장소를 교체해야합니다 .
피터 테일러

3

lingeling 의 C ++ 및 라이브러리

요약 : 새로운 접근 방식, 새로운 솔루션 없음 , 훌륭한 프로그램, 알려진 솔루션의 로컬 비 개선 가능성에 대한 흥미로운 결과. 아, 그리고 일반적으로 유용한 관찰.

SAT 기반 접근 방식을 사용하여 얇은 벽 대신 고정 셀과 반대쪽 모서리에 고정 시작 및 종료 위치가있는 4x4 미로의 유사한 문제를 완전히 해결할 수 있습니다. 그래서 나는이 문제에 대해 동일한 아이디어를 사용할 수 있기를 바랍니다. 그러나 다른 문제의 경우 2423 미로 만 사용했지만 (2083이면 충분 함) 길이 29의 솔루션이 있으며 SAT 인코딩은 수백만 개의 변수를 사용하여 며칠이 걸렸습니다.

그래서 두 가지 중요한 방법으로 접근 방식을 변경하기로 결정했습니다.

  • 처음부터 솔루션 검색을 고집하지 말고 솔루션 문자열의 일부를 수정하십시오. (어쨌든 단위 조항을 추가하여 쉽게 수행 할 수 있지만 내 프로그램을 사용하면 편안합니다.)
  • 처음부터 모든 미로를 사용하지 마십시오. 대신, 한 번에 해결되지 않은 미로를 하나씩 추가하십시오. 일부 미로는 우연히 해결되거나 이미 고려 된 미로가 해결 될 때 항상 해결됩니다. 후자의 경우 의미를 알 필요없이 추가되지 않습니다.

또한 적은 변수와 단위 절을 사용하도록 최적화를 수행했습니다.

이 프로그램은 @orlp를 기반으로합니다. 중요한 변화는 미로의 선택이었습니다.

  • 우선, 미로는 벽 구조와 시작 위치에 의해서만 주어집니다. (또한 도달 가능한 위치를 저장합니다.)이 기능 is_solution은 모든 도달 가능한 위치에 도달했는지 확인합니다.
  • (변경되지 않음 : 여전히 도달 할 수있는 위치가 4 이하인 미로를 사용하지 않습니다. 그러나 대부분은 다음 관찰에 의해 버려집니다.)
  • 미로가 세 개의 최상위 셀을 사용하지 않으면 위로 이동하는 미로와 같습니다. 그래서 우리는 그것을 떨어 뜨릴 수 있습니다. 3 개의 왼쪽 셀을 사용하지 않는 미로도 마찬가지입니다.
  • 도달 할 수없는 부품이 연결되어 있는지 여부는 중요하지 않으므로 도달 할 수없는 각 셀은 벽으로 완전히 둘러싸여 있어야합니다.
  • 더 큰 단일 경로 미로의 하위 미로 인 단일 경로 미로는 더 큰 단일 경로 미로가 해결 될 때 항상 해결되므로 필요하지 않습니다. 최대 7 크기의 각 단일 경로 미로는 더 큰 것의 일부이지만 (3x3에서는 여전히 적합) 크기가 아닌 단일 경로 미로는 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와 솔버는 무언가 등으로 프로그램을 컴파일 , 경로 곳이다 RESP. 예를 들어 둘 다 일 수 있습니다 . 아니면 그냥하지 않고 동일한 디렉토리에 넣고 할 수 및 옵션을 제공합니다.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많은 다음 *추가 단계를 원하는대로의. 첫 번째 시험으로, 당신이 가장 좋아하는 솔루션을 선택하고 일부 문자를로 바꿉니다 *. 이것은 놀라 울 정도로 높은 "일부"값에 대한 솔루션을 빠르게 찾습니다.

이 프로그램은 벽 구조와 시작 위치로 설명 된 추가 미로를 알려주고 도달 가능한 위치와 벽의 수를 알려줍니다. 미로는 이러한 기준에 따라 정렬되며 해결되지 않은 첫 번째 미가 추가됩니다. 따라서 대부분의 추가 미로는가 (9/4)있지만 때로는 다른 미로도 나타납니다.

나는 길이가 79 인 알려진 해결책을 취했으며 인접한 26 자의 각 그룹에 대해 25 자로 대체하려고했습니다. 또한 시작과 끝에서 13 글자를 제거하고 시작에서 13 글자와 끝에서 12 글자로 바꾸려고했습니다. 불행히도 모든 것이 만족스럽지 못했습니다. 길이 79가 최적이라는 지표로 이것을 사용할 수 있습니까? 아니요, 마찬가지로 길이 80 솔루션을 길이 79로 개선하려고 시도했지만 성공하지 못했습니다.

마지막으로 한 솔루션의 시작 부분을 다른 솔루션의 끝 부분과 하나의 대칭으로 변환 된 하나의 솔루션과 결합하려고했습니다. 이제 흥미로운 아이디어가 없어서 새로운 솔루션으로 이어지지는 않았지만 내가 가진 것을 보여 주기로 결정했습니다.


정말 재미있는 책이었습니다. 새로운 접근 방식과 확인해야 할 미로의 수를 줄이는 다양한 방법. 유효한 답변이 되려면 유효한 문자열을 포함해야합니다. 이 방법에 대한 현재 점수를 제공하기 위해 새로운 최단 문자열 일 필요는 없으며 길이가 유효한 모든 문자열입니다. 점수가 없으면 답변이 삭제 될 위험이 있기 때문에 이것을 언급하며 실제로 유지하고 싶습니다.
trichoplax

또한 오래된 관련 문제에 대한 최적의 길이 솔루션을 찾는 데 도움이됩니다 !
trichoplax
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.