요약 : 새로운 접근 방식, 새로운 솔루션 없음 , 훌륭한 프로그램, 알려진 솔루션의 로컬 비 개선 가능성에 대한 흥미로운 결과. 아, 그리고 일반적으로 유용한 관찰.
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. 예를 들어 둘 다 일 수 있습니다
. 아니면 그냥하지 않고 동일한 디렉토리에 넣고 할 수 및 옵션을 제공합니다.make
lingeling
g++ -std=c++11 -O3 -I ... -o m3sat m3sat.cc -L ... -llgl
...
lglib.h
liblgl.a
../lingeling-<version>
-I
-L
이 프로그램은, 하나 개의 필수 명령 행 인수를 문자열로 이루어진 N
, E
, S
, W
(고정 방향) 또는 *
. 그래서 당신은 78 개의 문자열 제공함으로써 크기 (78)의 일반적인 솔루션을 검색 할 수있다 *
(따옴표)들, 또는로 시작하는 솔루션을 검색 NEWS
을 사용하여 NEWS
많은 다음 *
추가 단계를 원하는대로의. 첫 번째 시험으로, 당신이 가장 좋아하는 솔루션을 선택하고 일부 문자를로 바꿉니다 *
. 이것은 놀라 울 정도로 높은 "일부"값에 대한 솔루션을 빠르게 찾습니다.
이 프로그램은 벽 구조와 시작 위치로 설명 된 추가 미로를 알려주고 도달 가능한 위치와 벽의 수를 알려줍니다. 미로는 이러한 기준에 따라 정렬되며 해결되지 않은 첫 번째 미가 추가됩니다. 따라서 대부분의 추가 미로는가 (9/4)
있지만 때로는 다른 미로도 나타납니다.
나는 길이가 79 인 알려진 해결책을 취했으며 인접한 26 자의 각 그룹에 대해 25 자로 대체하려고했습니다. 또한 시작과 끝에서 13 글자를 제거하고 시작에서 13 글자와 끝에서 12 글자로 바꾸려고했습니다. 불행히도 모든 것이 만족스럽지 못했습니다. 길이 79가 최적이라는 지표로 이것을 사용할 수 있습니까? 아니요, 마찬가지로 길이 80 솔루션을 길이 79로 개선하려고 시도했지만 성공하지 못했습니다.
마지막으로 한 솔루션의 시작 부분을 다른 솔루션의 끝 부분과 하나의 대칭으로 변환 된 하나의 솔루션과 결합하려고했습니다. 이제 흥미로운 아이디어가 없어서 새로운 솔루션으로 이어지지는 않았지만 내가 가진 것을 보여 주기로 결정했습니다.