最短のユニバーサル迷路出口文字列


48

正方形のセルのN×Nグリッド上の迷路は、各エッジが壁であるか壁でないかを指定することによって定義されます。外縁はすべて壁です。1つのセルはstartとして定義され、1つのセルはexitとして定義され、exitはstartから到達可能です。開始と終了が同じセルになることはありません。

開始点と終了点のどちらも迷路の外側の境界にある必要はないため、これは有効な迷路です。

中央のセルに出口がある3 x 3の迷路

「N」、「E」、「S」、「W」の文字列は、それぞれ北、東、南、西を移動しようとしていることを示します。壁によってブロックされている移動は、移動せずにスキップされます。文字列は終了(かかわらず、文字列が出口に達した後に継続するかどうか)に達している出口における開始結果からその文字列を適用する場合迷路。

触発され、このpuzzling.SE質問れるXNOR提供解くの証明可能な方法非常に 3迷路によって任意の3を出て、単一の文字列を検索することができ、長い文字列、書き込みコードを。

無効な迷路(同じセルで開始および終了、または開始から到達できない終了)を除くと、138,172の有効な迷路があり、文字列はそれぞれを終了する必要があります。

有効

文字列は次の条件を満たす必要があります。

  • 文字「N」、「E」、「S」、および「W」のみで構成されています。
  • 開始時に開始された場合、適用される迷路を終了します。

考えられるすべての迷路のセットには、考えられる有効な各開始点を持つ各考えられる迷路が含まれているため、これは、文字列が任意の有効な開始点から迷路を出るということを自動的に意味します。つまり、出口に到達できる開始点から。

勝ち

勝者は、最短の有効な文字列を提供し、それを生成するために使用されるコードを含む答えです。複数の回答がこの最短の文字列を提供する場合、その文字列の長さを最初に投稿したものが勝ちます。

これは、打ち勝つための500文字の文字列の例です。

SEENSSNESSWNNSNNNNWWNWENENNWEENSESSNENSESWENWWWWWENWNWWSESNSWENNWNWENWSSSNNNNNNESWNEWWWWWNNNSWESSEEWNENWENEENNEEESEENSSEENNWWWNWSWNSSENNNWESSESNWESWEENNWSNWWEEWWESNWEEEWWSSSESEEWWNSSEEEEESSENWWNNSWNENSESSNEESENEWSSNWNSEWEEEWEESWSNNNEWNNWNWSSWEESSSSNESESNENNWEESNWEWSWNSNWNNWENSNSWEWSWWNNWNSENESSNENEWNSSWNNEWSESWENEEENSWWSNNNNSSNENEWSNEEWNWENEEWEESEWEEWSSESSSWNWNNSWNWENWNENWNSWESNWSNSSENENNNWSSENSSSWWNENWWWEWSEWSNSSWNNSEWEWENSWENWSENEENSWEWSEWWSESSWWWNWSSEWSNWSNNWESNSNENNSNEWSNNESNNENWNWNNNEWWEWEE

これを寄付してくれたorlpに感謝します。


リーダーボード

リーダーボード

等しいスコアは、そのスコアの投稿順にリストされます。特定の回答のスコアは時間とともに更新される可能性があるため、これは必ずしも回答が投稿された順序ではありません。


裁判官

次に、コマンドライン引数として、またはSTDINを介してNESWの文字列を受け取るPython 3バリデータを示します。

無効な文字列の場合、失敗した迷路の視覚的な例を示します。


3
これは本当にすてきな質問です。最短の文字列が1つありますか(または、文字列の数と短い答えができないことの証明)ありますか?もしそうなら、あなたはそれを知っていますか?
アレックスヴァンリュー

1
@AlexReinking yesはい、開始は9つのセルのいずれかであり、出口は9つのセルのいずれかであり、同じセルでなく、出口は開始から到達可能です。
-trichoplax

1
このstackoverflowの質問に少し似て:stackoverflow.com/questions/26910401/... -しかし、開始と終了のセルが2423に可能な迷路の数を減少させる1で左上と右下の、ある
schnaader

1
@proudhaskellerどちらの方法でも有効な質問になります。n = 3でスコア付けされた一般的なケースでは、より一般化されたコードが必要になります。この特定のケースでは、一般的なnには適用されない最適化が可能になり、それが私が選択する方法です。
-trichoplax

2
誰もが正規表現への最短受け入れ文字列を見つけることとして、この問題に近づいていると考えましたか?正規表現に変換する前に問題の数を大幅に減らす必要がありますが、理論的には検証可能な最適なソリューションを見つけることができます。
カイルマコーミック

回答:


37

C ++、97 95 93 91 86 83 82 81 79文字

NNWSWNNSENESESWSSWNSEENWWNWSSEWWNENWEENWSWNWSSENENWNWNESENESESWNWSESEWWNENWNEES

私の戦略は非常にシンプルです。有効なシーケンスの要素を成長、縮小、交換、突然変異させることができる進化アルゴリズムです。私の進化のロジックは、@ 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のアプローチを取り、それに独自のスピンをかけたので、この答えを投稿するのをためらっていました。この文字列は、疑似ランダム長500のソリューションから始めて発見されました。現在の記録を破る前に、かなりの数のシードが試されました。

唯一の新しい主要な最適化は、迷路の3分の1だけを見るということです。迷路の2つのカテゴリは検索から除外されます。

  • <= 7正方形が到達可能な迷路
  • すべての到達可能な正方形が単一のパス上にあり、開始/終了が両端にない迷路

アイデアは、残りの迷路を解決する文字列も上記を自動的に解決する必要があるということです。私はこれが2番目のタイプには当てはまると確信していますが、最初のタイプには当てはまらないので、出力には個別にチェックする必要があるいくつかの誤検知が含まれます。しかし、これらの誤検知は通常約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

迷路をチェックする必要がないことを理解するというあなたのアイデアが好きです。どの迷路が冗長チェックであるかを判断するプロセスを何らかの方法で自動化できますか?直感的に推測できる迷路よりも多くの迷路が表示されるかどうか知りたいのですが…
-trichoplax

始点が一方の端にない場合にパスグラフをチェックする必要がない理由は何ですか?フィニッシュが一方の端にない場合は正当化するのが簡単であり、フィニッシュがカット頂点である場合をチェックする必要がないように強化できますが、開始頂点の削除を正当化する方法がわかりません。
ピーターテイラー

@PeterTaylorもう少し考えた後、理論的にはあなたは正しいです、あなたがそのように除去できないいくつかの迷路があります。ただし、3x3では、これほど長い文字列では問題ないようです。
orlp

2
@ orlp、Sp3000はチャットで証拠をスケッチしました。パスグラフは特殊なケースです。セルの番号をパス0n沿って付け直し、文字列Sからに移動0するとしnます。次にS、任意の中間セルcからに移動しnます。そうでないと仮定します。ましょうa(i)i始まり、0b(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

C ++とリンゲリングのライブラリ

要約:新しいアプローチ、新しいソリューションはありません、プレイする素敵なプログラム、および既知のソリューションのローカルな改善不能性の興味深い結果。ああ、そしていくつかの一般的に有用な観察。

SAT ベースのアプローチを使用する と、薄壁の代わりにセルがブロックされ、反対側のコーナーの開始位置と終了位置が固定された4x4迷路の同様の問題を完全に解決できました 。したがって、この問題に対して同じアイデアを使用できるようになりたいと思いました。ただし、他の問題では2423個の迷路のみを使用し(その間は2083個で十分であることが観察されています)、29の長さのソリューションがありますが、SATエンコードには数百万の変数が使用され、解決には数日かかりました。

そこで、2つの重要な方法でアプローチを変更することにしました。

  • ゼロからソリューションを検索することを主張するのではなく、ソリューション文字列の一部を修正することができます。(とにかく単位句を追加することで簡単に実行できますが、私のプログラムでは快適に実行できます。)
  • 最初からすべての迷路を使用しないでください。代わりに、未解決の迷路を一度に1つずつ追加します。一部の迷路は偶然に解決される場合もあれば、すでに検討されている迷路が解決されると常に解決される場合もあります。後者の場合、意味を知る必要なく、追加されることはありません。

また、より少ない変数と単位句を使用するようにいくつかの最適化を行いました。

プログラムは@orlpに基づいています。重要な変更は迷路の選択でした:

  • まず、迷路は壁の構造と開始位置によってのみ与えられます。(また、到達可能な位置も保存します。)関数is_solutionは、到達可能なすべての位置に到達したかどうかをチェックします。
  • (変更なし:到達可能な位置が4つ以下の迷路をまだ使用していません。しかし、それらのほとんどは、次の観察結果によって破棄されます。)
  • 迷路が上の3つのセルのいずれも使用しない場合、それは上にシフトされた迷路と同等です。そのため、ドロップできます。同様に、左の3つのセルを使用しない迷路の場合も同様です。
  • 到達不能な部品が接続されているかどうかは問題ではないため、到達不能な各セルは完全に壁で囲まれていると主張します。
  • 大きなパスの迷路のサブ迷路であるシングルパスの迷路は、大きなパスの迷路が解決されるときに常に解決されるため、必要ありません。サイズが最大7の各単一パス迷路は、より大きなものの一部です(まだ3x3に収まります)が、サイズ8の単一パス迷路はありません。簡単にするために、サイズが8未満の単一パスの迷路をドロップします(そして、私はまだ極限点のみを開始位置と見なす必要があることを使用しています。プログラムの。)

このようにして、開始位置で合計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

プログラムは、1つの必須コマンドライン引数を取る文字列は、以下からなるNESW(固定された方向)または*。したがって、78 *の文字列(引用符で囲む)を指定してサイズ78の一般的なソリューションを検索するかNEWS、使用NEWSして*から追加のステップに必要な数のsが続くソリューションを検索できます。最初のテストとして、お気に入りのソリューションを使用して、文字の一部をに置き換えます*。これにより、驚くほど高い「一部」の値に対する解決策が迅速に見つかります。

プログラムは、壁の構造と開始位置によって記述される迷路を追加し、到達可能な位置と壁の数も示します。迷路はこれらの基準でソートされ、最初の未解決のものが追加されます。したがって、ほとんどの追加された迷路には(9/4)がありますが、時には他の迷路も表示されます。

長さ79の既知の解決策を採用し、隣接する26文字の各グループについて、それらを25文字に置き換えようとしました。また、先頭と末尾から13文字を削除し、先頭の任意の13文字と末尾の任意の12文字に置き換えました。逆も同様です。残念ながら、それはすべて不満足な結果となりました。それで、長さ79が最適であることの指標としてこれをとることができますか?いいえ、同様に長さ80のソリューションを長さ79に改善しようとしましたが、これも成功しませんでした。

最後に、1つのソリューションの開始と他のソリューションの終了を組み合わせてみました。また、1つのソリューションを対称性の1つによって変換しました。今、私は面白いアイデアを使い果たしているので、新しいソリューションにつながらなかったとしても、私が持っているものを紹介することにしました。


それは本当に興味深い読み物でした。新しいアプローチと、チェックする迷路の数を減らすさまざまな方法の両方。有効な回答になるには、有効な文字列を含める必要があります。このアプローチの現在のスコアを得るために、新しい最短の文字列である必要はなく、任意の長さの有効な文字列である必要があります。スコアがなければ、答えは削除のリスクがあるため、これについて言及します。
trichoplax

また、関連する古い課題に最適な長さのソリューションを見つけるための素晴らしい仕事です!
trichoplax
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.