En kısa evrensel labirent çıkış dizesi


48

N ile N kare hücreli bir ızgara üzerinde bir labirent, her bir kenarın bir duvar olup olmadığını belirterek tanımlanır. Tüm dış kenarlar duvardır. Bir hücre başlangıç , bir hücre çıkış olarak tanımlanır ve çıkışa başlangıçtan erişilebilir. Başlangıç ​​ve çıkış asla aynı hücre değildir.

Ne başlangıç ​​ne de çıkış labirentin dış sınırında olması gerekmediğine dikkat edin, bu geçerli bir labirenttir:

Merkez hücreye çıkışlı 3'e 3'lük bir labirent

'N', 'E', 'S' ve 'W' dizileri sırasıyla Kuzey, Doğu, Güney ve Batı'yı taşımayı denediğini gösterir. Bir duvar tarafından engellenen bir hareket, hareket olmadan atlanır. Eğer bir dizeden başlayarak o dizgeye uygulanırsa dizgeden çıkılır (dizgeden çıkışa ulaştıktan sonra devam edilip edilmediğine bakılmaksızın) bir dize çıkar.

Esinlenen bu puzzling.SE soruya kendisi için XNOR bir sağladı çözme kanıtlanabilir yöntemini bir ile çok tek bir sicim bulabilirsiniz, uzun bir dize yazma kodu olduğunu çıkışlar 3 labirent tarafından herhangi 3.

Geçersiz labirentler hariç (aynı hücrede başlat ve çık, veya başlangıçtan erişilemiyorsa çık) 138,172 geçerli labirent var ve dize her birinden çıkmalı.

Geçerlik

Dize aşağıdakileri sağlamalıdır:

  • Sadece 'N', 'E', 'S' ve 'W' karakterlerinden oluşur.
  • Başlangıçta başlarsa, uygulandığı herhangi bir labirentten çıkar.

Tüm olası labirentler seti, olası her geçerli başlangıç ​​noktasıyla birlikte her olası labirenti içerdiğinden, bu otomatik olarak dizginin herhangi bir labirentten herhangi bir geçerli başlangıç ​​noktasından çıkacağı anlamına gelir . Yani, çıkışın erişilebilir olduğu herhangi bir başlangıç ​​noktasından.

Kazanan

Kazanan, en kısa geçerli dizeyi sağlayan cevaptır ve onu üretmek için kullanılan kodu içerir. Eğer cevaplardan birden fazla bu en kısa uzunluğa sahip bir dize sağlarsa, o dize uzunluğunu ilk gönderen kazanır.

Örnek

İşte 500 karakter uzunluğunda, size yenecek bir şey vermek için örnek bir dizge:

SEENSSNESSWNNSNNNNWWNWENENNWEENSESSNENSESWENWWWWWENWNWWSESNSWENNWNWENWSSSNNNNNNESWNEWWWWWNNNSWESSEEWNENWENEENNEEESEENSSEENNWWWNWSWNSSENNNWESSESNWESWEENNWSNWWEEWWESNWEEEWWSSSESEEWWNSSEEEEESSENWWNNSWNENSESSNEESENEWSSNWNSEWEEEWEESWSNNNEWNNWNWSSWEESSSSNESESNENNWEESNWEWSWNSNWNNWENSNSWEWSWWNNWNSENESSNENEWNSSWNNEWSESWENEEENSWWSNNNNSSNENEWSNEEWNWENEEWEESEWEEWSSESSSWNWNNSWNWENWNENWNSWESNWSNSSENENNNWSSENSSSWWNENWWWEWSEWSNSSWNNSEWEWENSWENWSENEENSWEWSEWWSESSWWWNWSSEWSNWSNNWESNSNENNSNEWSNNESNNENWNWNNNEWWEWEE

Bunu bağışladığınız için teşekkürler .


Liderler Sıralaması

Liderler Sıralaması

Eşit puanlar, bu puanın kaydedilme sırasına göre listelenir. Bu ille de verilen cevapların puanları zamanla güncellenebildiğinden, cevapların kaydedilme sırası zorunlu değildir.


Hakim

İşte bir NESW dizesini komut satırı argümanı olarak veya STDIN aracılığıyla alan bir Python 3 doğrulayıcısı .

Geçersiz bir dize için, bu başarısız olduğu bir labirent görsel bir örnek verecektir.


3
Bu gerçekten temiz bir soru. En kısa bir dize var mı (veya çok sayıda dize var ve daha kısa cevaplar olamayacağına dair bir kanıt)? Ve eğer öyleyse, bunu biliyor musun?
Alex Van Liew

1
@AlexReinking evet, başlangıç ​​9 hücreden herhangi biri olabilir ve çıkış aynı hücrede olmadığı sürece 9 hücreden herhangi biri olabilir ve çıkışa başlangıçtan erişilebilir.
trichoplax

1
Biraz bu stackoverflow soruya benzer: stackoverflow.com/questions/26910401/... - ama son hücresi 2423. mümkün labirent sayısını azalttığını birinde sol üst ve sağ alt vardır başlayıp
schnaader

1
@ proudhaskeller her iki şekilde de geçerli bir soru olacaktır. N = 3 için puanlanan genel durum daha genel kod gerektirir. Bu özel durum, genel n'ye uygulanmayan optimizasyonlara izin verir ve bunu sormayı seçtiğim yol budur.
trichoplax

2
Birileri bu soruna yaklaşmayı, normal bir ifadeye kabul edilen en kısa dizeyi bulmak olarak değerlendirdi mi? Regex'lere dönüştürmeden önce sorun sayısında bir LOT azaltma gerektirir, ancak teorik olarak doğrulanabilir en uygun çözümü bulabilir.
Kyle McCormick,

Yanıtlar:


37

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

NNWSWNNSENESESWSSWNSEENWWNWSSEWWNENWEENWSWNWSSENENWNWNESENESESWNWSESEWWNENWNEES

Stratejim oldukça basittir - geçerli sekansları büyüten, daraltan, öğelerini değiştiren ve değiştirebilen bir evrim algoritması. Benim evrim mantığım, şimdi neredeyse @ Sp3000'inkiyle aynı, benimkinde bir gelişme olduğu gibi

Bununla birlikte, labirent mantığını uygulamam oldukça şık. Bu, dizelerin kabarma hızında geçerli olup olmadığını kontrol etmeme izin veriyor. Yorumlarınız, bakarak anlamaya çalışın do_moveve Mazeyapıcı.

#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
Onaylandı geçerli. Çok etkilendim - bu kadar kısa ipler görmeyi beklemiyordum.
trichoplax

2
Sonunda gcc'yi kurmaya ve bunu kendim çalıştırmaya başladım. Dizeleri mutasyona
uğratıp

1
@trichoplax Size eğlenceli olduğunu söyledim :)
orlp

2
@AlexReinking Cevabımı belirtilen uygulama ile güncelledim. Sökmeye bakarsanız dal veya yük olmadan sadece bir düzine talimat göreceksiniz : coliru.stacked-crooked.com/a/3b09d36db85ce793 .
orlp

2
@AlexReinking Yapıldı. do_moveşimdi delicesine hızlı.
orlp

16

Python 3 + PyPy, 82 80 karakter

SWWNNSENESESWSSWSEENWNWSWSEWNWNENENWWSESSEWSWNWSENWEENWWNNESENESSWNWSESESWWNNESE

Bu cevabı göndermek için tereddüt ettim çünkü temelde orlp'un yaklaşımını benimsedim ve kendi dönüşümü yaptım. Bu sicim, sahte bir uzunluk 500 çözümü ile başlayarak bulundu - mevcut rekoru kırabilmemden önce epeyce tohum denendi.

Tek yeni ana optimizasyon labirentlerin sadece üçte birine bakmam. İki labirent kategorisi aramadan hariç tutuldu:

  • Karelere <= 7ulaşılabilen labirentler
  • Tüm erişilebilir karelerin tek bir yolda olduğu ve başlangıç ​​/ bitişin her iki uçta olmadığı labirentler

Fikir, labirentlerin geri kalanını çözen herhangi bir dizenin de yukarıdakileri otomatik olarak çözmesi gerektiğidir. Bunun ikinci tip için doğru olduğuna ikna oldum, ancak ilk olarak kesinlikle doğru değil, bu nedenle çıktı ayrı ayrı kontrol edilmesi gereken bazı yanlış pozitifler içerecektir. Bu yanlış pozitif genellikle ancak yaklaşık 20 labirenti özlüyor, bu yüzden hız ve doğruluk arasında iyi bir tradeoff olacağını düşündüm, ve aynı zamanda iplere mutasyon için biraz daha nefes alan bir yer verecek.

Başlangıçta uzun bir araştırma sezgiselliği listesine girdim, ama korkunç bir şekilde hiçbiri 140'tan daha iyi bir şey bulamadı.

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

Onaylandı geçerli. Güzel iyileştirmeler :)
trichoplax

Bazı labirentlerin kontrol edilmesine gerek olmadığını anlama fikrinizi seviyorum. Hangi labirentlerin gereksiz çekler olduğunu belirleme işlemini bir şekilde otomatikleştirebilir misiniz? Bunun sezgisel olarak çıkarılabilecek olanlardan daha fazla labirent gösterip göstermeyeceğini merak ediyorum ...
trichoplax

Başlangıç ​​bir ucunda olmayan yol grafiklerini kontrol etmek zorunda kalmamanızın nedeni nedir? Sonun bir ucunda olmadığı durumun doğrulanması kolaydır ve bitişin kesik bir köşe olduğu durumları kontrol etmemeye gerek kalmadan güçlendirilebilir, ancak başlangıç ​​köşelerinin kaldırılmasının nasıl doğrulanacağını göremiyorum.
Peter Taylor

@PeterTaylor Daha fazla düşündükten sonra, teorik olarak haklısınız, böyle ortadan kaldıramayacağınız labirentler var. Bununla birlikte, 3x3'te, bu kadar uzun dizeler için farketmez gibi görünüyor.
orlp

2
@ orlp, Sp3000 sohbette bir kanıtı çizdi. Yol grafikleri özel bir durumdur. Hücreleri yeniden numaralandır 0için nyol boyunca ve dize varsayalım Ssizi alır 0için n. Sonra Ssizi herhangi bir ara hücreden bir cyere götürür n. Varsayalım. Başlayarak başlayan ve başlayan adımlardan a(i)sonra pozisyon olsun . Sonra her adım değişir ve en fazla 1 ve . Öyle en küçüğünü al . Açıkçası yoksa senkronize olurlar, bu yüzden aynı yönde hareket ederek adım adım yer değiştirmeleri gerekir . i0b(i)ca(0) = 0 < b(0)aba(|S|) = n > b(|S|)ta(t) >= b(t)a(t) != b(t)t
Peter Taylor

3

C ++ ve dil bilen kütüphaneler

Özet: Yeni bir yaklaşım, yeni bir çözüm yok , oynamak için güzel bir program ve bilinen çözümlerin yerel olarak iyileştirilememesi konusundaki ilginç bazı sonuçlar. Oh, ve bazı genel olarak yararlı gözlemler.

SAT tabanlı bir yaklaşım kullanarak , ince duvarlar yerine bloke hücreli 4x4 labirentlerinde ve ters köşelerde sabit başlangıç ​​ve çıkış pozisyonlarında benzer problemleri tamamen çözebildim . Bu yüzden bu problem için aynı fikirleri kullanabilmeyi umuyordum. Bununla birlikte, diğer problem için sadece 2423 labirent kullandım (bu arada 2083'ün yeterli olduğu gözlendi) ve 29 uzunluğundaki bir çözüme sahipti, SAT kodlaması milyonlarca değişken kullandı ve günlerini çözdü.

Bu yüzden yaklaşımı iki önemli şekilde değiştirmeye karar verdim:

  • Sıfırdan bir çözüm aramakta ısrar etmeyin, ancak çözüm dizesinin bir bölümünü düzeltmeye izin verin. (Ünite cümleleri ekleyerek yine de yapmak kolaydır, ancak programım bunu yapmayı rahatlatıyor.)
  • Tüm labirentleri en baştan kullanmayın. Bunun yerine, art arda bir defada çözülmemiş bir labirent ekleyin. Bazı labirentler tesadüfen çözülebilir veya önceden düşünülenler çözüldüğünde her zaman çözülürler. İkinci durumda, anlamını bilmek zorunda kalmadan asla eklenmeyecek.

Ayrıca daha az değişken ve birim cümlecikleri kullanmak için bazı optimizasyonlar yaptım.

Program, @ orlp’e dayanmaktadır. Önemli bir değişiklik labirent seçimi oldu:

  • Öncelikle, labirentler duvar yapılarına ve sadece başlangıç ​​pozisyonlarına göre verilmiştir. (Ayrıca ulaşılabilir pozisyonları da saklarlar.) Fonksiyon, ulaşılabilir is_solutiontüm pozisyonlara ulaşılıp ulaşılmadığını kontrol eder.
  • (Değişmedi: hala sadece 4 veya daha az erişilebilir pozisyona sahip labirentler kullanmıyor. Fakat çoğu yine de aşağıdaki gözlemlerle atılacaktı.)
  • Bir labirent en üstteki üç hücreden birini kullanmıyorsa, yukarı kaydırılan bir labirente eşdeğerdir. Böylece bırakabiliriz. Aynı şekilde, sol üç hücreden herhangi birini kullanmayan bir labirent için.
  • Ulaşılamayan parçaların birbirine bağlı olup olmaması önemli değildir, bu yüzden ulaşılamayan her hücrenin tamamen duvarlarla çevrili olduğu konusunda ısrar ediyoruz.
  • Daha büyük bir tek yol labirentinin alt kümesi olan tek bir yol labirenti, daha büyük bir çözümlendiğinde her zaman çözülür, bu yüzden buna ihtiyacımız olmaz. En fazla 7 büyüklüğündeki her bir tek yol labirenti, daha büyük olanın bir parçasıdır (hala 3x3'te). Basit olması için, 8'den küçük tek labirentli labirentlere bırakalım. (Ve hala sadece aşırı noktaların başlangıç ​​pozisyonu olarak kabul edilmesi gerektiğini düşünüyorum. Programın

Bu sayede başlangıç ​​pozisyonları ile toplam 10772 labirent elde ediyorum.

İşte program:

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

İlk configure.shve benzeri çözücü, daha sonra bir şeyle programı derlemek nerede, nerede yoludur solunum. mesela ikisi de olabilir . Ya da sadece onları aynı dizine yerleştirin ve ve seçenekleri olmadan yapın .makelingelingg++ -std=c++11 -O3 -I ... -o m3sat m3sat.cc -L ... -llgl...lglib.hliblgl.a../lingeling-<version>-I-L

Program, bir zorunlu komut satırı argüman alır dize oluşan N, E, S, W(sabit tarifini) ya da *. Böylece, 78 *s (tırnak işaretleri içinde) bir dize vererek genel bir çözüm boyutu 78'i arayabilir veya daha sonra ek adımlar için istediğiniz kadar s'yi NEWSkullanarak başlayarak bir çözüm arayabilirsiniz . İlk test olarak, favori çözümünüzü alın ve bazı harfleri ile değiştirin . Bu, şaşırtıcı bir şekilde yüksek bir "bazı" değeri için hızlı bir çözüm bulur.NEWS**

Program, hangi duvarın labirentine eklendiğini, duvar yapısı ve başlama pozisyonu ile anlatıldığını ve aynı zamanda ulaşılabilir pozisyonların ve duvarların sayısını verir. Labirentler bu kritere göre sıralanır ve ilk çözülmeyenler eklenir. Bu nedenle çoğu labirentte labirent vardır (9/4), ancak bazen diğerleri de ortaya çıkar.

Bilinen uzunluktaki 79 çözümünü aldım ve komşu 26 harften oluşan her grup için, her birini 25 harften değiştirmeye çalıştım. Ayrıca, 13 harfi baştan ve sondan kaldırmaya, başlarında 13, sonunda da 12 harfle değiştirmeye çalıştım. Ne yazık ki, hepsi tatmin edilemez geldi. Peki, bunu 79 uzunluğunun en uygun olduğunu gösterebilir miyiz? Hayır, benzer şekilde uzunluk 80 çözümünü uzunluk 79'a yükseltmeye çalıştım ve bu da başarılı olamadı.

Sonunda bir çözümün başlangıcını diğerinin sonu ile ve ayrıca simetrilerden biri tarafından dönüştürülmüş bir çözümle birleştirmeyi denedim. Şimdi ilginç fikirlerim tükeniyor, bu yüzden yeni çözümlere yol açmasa da elimde olanı göstermeye karar verdim.


Bu gerçekten ilginç bir okumaydı. Hem yeni yaklaşım hem de kontrol edilecek labirent sayısını azaltmanın farklı yolları. Geçerli bir cevap olması için bunun geçerli bir dize içermesi gerekir. Bu yaklaşım için geçerli bir puan vermesi için kısa bir dize, sadece herhangi bir uzunlukta geçerli bir dize olması gerekmez. Bunu söylüyorum çünkü bir skor olmadan cevap silinme riski altında olacak ve gerçekten kaldığını görmek istiyorum.
trichoplax

Ayrıca, eski ile ilgili zorluklar için en uygun uzunluk çözümünü bulma konusunda iyi çalışmalar !
trichoplax
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.