N-皇后马奎因


21

众所周知的N皇后问题有一个变体,其中涉及皇后和骑士,并且据说“难度更大” 1。问题陈述如下:

您必须在棋chess上放置相等数量的骑士♞和皇后♛,以使任何棋子都不会攻击其他棋子。您可以在板上放置的最大块数是多少,可以用几种不同的方法来做?

在此挑战中,将为您提供3到32之间的输入n(以最适合您的语言的方式)。对于给定的n,上述问题可能有零个或更多个解决方案。如果没有解决方案,则必须不输出/返回任何内容nil空字符串false,...)。否则,您必须给出两个结果:

  1. 尺寸为n的解决方案板(请参阅下文),在没有任何棋子受到攻击的情况下无法添加皇后或骑士棋子。皇后和骑士人数相等
  2. 要运行的程序的源,该程序不接受任何输入,并且以相同的格式给出(i)具有相同大小n的另一个解决方案(或不提供任何东西),以及(ii)用于下一个解决方案的另一个程序(依此类推) ...)。

注意:

  • 程序序列必须永远不会返回同一块板两次,必须涵盖大小为n的问题的所有可能解决方案,并最终必须终止(不产生输出)。
  • 您可以返回两个值,返回一个并打印另一个值,或者打印两个返回值。
  • 但是,如果您同时打印电路板和下一个程序,则不能将电路板视为下一个程序的一部分(建议您在注释中打印电路板,或者同时使用标准输出和错误流)。
  • 程序的返回值必须是字符串,而不是闭包。

板格式

  • 一块木板是大小为n的正方形。
  • 棋盘格可以是空的,皇后或骑士。
  • 您必须为每种单元格选择不同的值(即,在印刷电路板时可以使用Q,N以外的其他符号)。
  • 如果返回一个非字符串板,则它必须是该板的n 2个值的有序集合(例如,矩阵,向量或以行/列为主的列表,...)。
  • 如果要打印板,则可以将其打印为正方形或线条。例如,可以按以下方式打印大小为4的解决方案板(不需要空格;可以根据需要选择符号):

    Q - - -
    - - - -
    - - - -
    - - N -
    

    如果有这种感觉,您还可以输出:

    ♛ · · ·
    · · · ·
    · · · ·
    · · ♞ ·
    

    ...但这足够了:

    Q-------------N-
    

    您可以按行优先或列优先顺序遍历单元无关紧要,因为存在对称解。例如,n = 4的解决方案是:

    Q------N--------
    Q----------N----
    Q------------N--
    Q-------------N-
    -Q----------N---
    -Q------------N-
    -Q-------------N
    --Q---------N---
    --Q----------N--
    --Q------------N
    ---QN-----------
    ---Q----N-------
    ---Q---------N--
    ---Q----------N-
    ---NQ-----------
    ----Q------N----
    ----Q----------N
    N------Q--------
    -------QN-------
    -------Q----N---
    ---N----Q-------
    -------NQ-------
    --------Q------N
    N----------Q----
    ----N------Q----
    -----------QN---
    -N----------Q---
    --N---------Q---
    -------N----Q---
    -----------NQ---
    N------------Q--
    --N----------Q--
    ---N---------Q--
    N-------------Q-
    -N------------Q-
    ---N----------Q-
    -N-------------Q
    --N------------Q
    ----N----------Q
    --------N------Q
    

您也可以将n = 5的解看做矩阵;板中含有#qn符号,这是不同类型的空细胞(见我的回答如下)。我计数了2836个板,其中n = 6,就像在Sleafar的答案中一样(我在减少字节数时引入了一个错误,但现在已修复)。

非常感谢Sleafar在我的代码中发现的不是一个bug,而是两个bug。

得分

以字节为单位的最短代码获胜。

我们测量第一个程序的大小,该程序接受n


1皇后和骑士,作者:Roger KW Hui (注意!包含一个解决方案)


4
也许您应该为此悬赏。老实说,如果没有quine部分,这个问题就够困难了。
mbomb007 '16

我们可以使用Q,N和-以外的任何符号来表示皇后和骑士并且为空,只要它们是不同的?
致命

@Fatalize是的,可以肯定
coredump

1
@coredump我的意思是阅读函数的内容。我将其视为“是的,允许您阅读自己的源代码和/或函数内容”。(我的解决方案依赖
于此

1
@coredump如果我正确理解了挑战,那么您对于n = 6的参考解决方案包含无效条目(例如-------------------------N--------Q-,由于可以添加更多片段,因此是无效的:)Q--------N---------------N--------Q-
Sleafar '16

Answers:


2

Groovy,515个字节

X=0;Y="N="+args[0]+";M=N*N;S=[];def f(b,i,j,v){(i..<j).findAll{k->!(0..<M).any{l->w=b[l];r=(k.intdiv(N)-l.intdiv(N)).abs();c=(k%N-l%N).abs();s=v+w;w>0&&(k==l||(r==0||c==0||r==c?s<4:r<3&&c<3&&s>2))}}.collect{a=b.clone();a[it]=v;[it,a]}};def r(b,q,n){f(b,q,M,1).each{i->f(i[1],n,M,2).each{j->if(f(j[1],0,M,1).any{f(it[1],0,M,2)}){r(j[1],i[0],j[0])}else{S.add(j[1])}}}};r(new int[M],0,0);if(x<S.size()){sprintf('//%s%cX=%d;Y=%c%s%c;print(Eval.xy(X,Y,Y))',S[x].toString(),10,x+1,34,y,34)}else{''}";print(Eval.xy(X,Y,Y))

测试中

提供n作为命令行参数:

groovy qak.groovy 4

输出的第一行始终是注释的解决方案(0 =空,1 =女王,2 =骑士),其后是第二行中的代码:

//[1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]
X=1;Y="N=4;M=N*N;S=[];def f(b,i,j,v){(i..<j).findAll{k->!(0..<M).any{l->w=b[l];r=(k.intdiv(N)-l.intdiv(N)).abs();c=(k%N-l%N).abs();s=v+w;w>0&&(k==l||(r==0||c==0||r==c?s<4:r<3&&c<3&&s>2))}}.collect{a=b.clone();a[it]=v;[it,a]}};def r(b,q,n){f(b,q,M,1).each{i->f(i[1],n,M,2).each{j->if(f(j[1],0,M,1).any{f(it[1],0,M,2)}){r(j[1],i[0],j[0])}else{S.add(j[1])}}}};r(new int[M],0,0);if(x<S.size()){sprintf('//%s%cX=%d;Y=%c%s%c;print(Eval.xy(X,Y,Y))',S[x].toString(),10,x+1,34,y,34)}else{''}";print(Eval.xy(X,Y,Y))

以下脚本可用于自动化测试(再次提供n作为参数):

#!/bin/bash
set -e
test -n "$1"
groovy qak.groovy "$1" > t
while test -s t; do
    head -n1 t
    groovy t > t2
    mv t2 t
done

因为我试图使解决方案尽可能的小,所以它非常慢(请参阅下面的详细信息)。在此版本中,我仅测试了n = 4,以查看是否可以进行奎尼化。

结果

n = 4:40 个解转换格式
n = 5: 172个解转换格式
n = 6: 2836个解转换格式

算法

这是该解决方案的一个略松散的非Quine版本:

N=args[0] as int
M=N*N
S=[]

/**
 * Generate a list of valid posibilities to place a new piece.
 * @param b Starting board.
 * @param i Start of the index range to check (inclusive).
 * @param j End of the index range to check (exclusive).
 * @param v Value of the new piece (1=queen, 2=knight).
 * @return A pair with the index of the new piece and a corresponding board for each possibility.
 */
def f(b,i,j,v){
    (i..<j).findAll{k->
        !(0..<M).any{l->
            w=b[l]
            r=(k.intdiv(N)-l.intdiv(N)).abs()
            c=(k%N-l%N).abs()
            s=v+w
            w>0&&(k==l||(r==0||c==0||r==c?s<4:r<3&&c<3&&s>2))
        }
    }.collect{
        a=b.clone();a[it]=v;[it,a]
    }
}

/**
 * Recursively look for solutions.
 * @param b Starting board.
 * @param q Start of the index range to check for queens.
 * @param n Start of the index range to check for knights.
 */
def r(b,q,n){
    f(b,q,M,1).each{i->
        f(i[1],n,M,2).each{j->
            if(f(j[1],0,M,1).any{f(it[1],0,M,2)}){
                r(j[1],i[0],j[0])
            }else{
                S.add(j[1])
            }
        }
    }
}

r(new int[M],0,0)
S.each{println(it)}

量化

我在这里使用了一种非常简单的方法来减小代码大小。

X=0;Y="...";print(Eval.xy(X,Y,Y))

变量X保留下一步要打印的解决方案的索引。Y持有上述算法的修改副本,该副本用于计算所有解决方案,然后仅选择其中一个解决方案,这就是其速度如此之慢的原因。该解决方案的优点是不需要太多其他代码。存储在Y中的代码在Eval类的帮助下执行(不需要真值)。

修改后的代码输出X指向的解决方案,增加X并附加其自身的副本:

//[...]
X=1;Y="...";print(Eval.xy(X,Y,Y))

我还尝试将所有解决方案输出为第二步的代码,但是对于n = 6,它为Groovy生成了太多代码以至于无法处理。


好的答案,干得好。
coredump

6

常见的Lisp,737

自我回答

(lambda(n &aux(d 1))#2=(catch'$(let((s(* n n))(c d))(labels((R(w % @ b ! &aux r h v a)(loop for u from % below s do(setf h(mod u n)v(floor u n)a #4=(aref b u))(when(< 0(logand a w)4)(and(= 6 w)!(throw'! t))(let((b(copy-seq b))(o 5))(loop for(K D)on'(-1 -2 -1 2 1 -2 1 2)for y =(+ K v)for x =(+(or D -1)h)for u =(and(< -1 y n)(< -1 x n)(+(* y n)x))if u do #1=(if(< #4#4)(setf #4#(logand #4#o(if(= w o)3 0)))))(#8=dotimes(y N)(#8#(x N)(let((u(+(* y n)x))(o 6))(if(or(= x h)(= y v)(=(abs(- h x))(abs(- v y))))#1#))))(setf #4#w r(or(cond((= w 5)(R 6 @ U b !))((R 5 @ U b())t)((catch'!(R 5 0 0 b t))t)(t(and(=(decf c)0)(incf d)(or(format t"~%(lambda(&aux(n ~A)(d ~A))~%~S)"n d'#2#)(throw'$ B)))t))r)))))r))(R 5 0 0(fill(make-array s)3)())))))

将以上内容粘贴到REPL中,这将返回一个函数对象:

#<FUNCTION (LAMBDA (N &AUX (D 1))) {1006D1010B}>

调用它(星号绑定到最后返回的值):

QN> (funcall * 4)

这会将以下内容打印到标准输出:

(lambda(&aux(n 4)(d 2))
#1=(CATCH '$
 (LET ((S (* N N)) (C D))
   (LABELS ((R (W % @ B ! &AUX R H V A)
              (LOOP FOR U FROM % BELOW S
                    DO (SETF H (MOD U N)
                             V (FLOOR U N)
                             A #2=(AREF B U)) (WHEN (< 0 (LOGAND A W) 4)
                                                (AND (= 6 W) !
                                                     (THROW '! T))
                                                (LET ((B (COPY-SEQ B))
                                                      (O 5))
                                                  (LOOP FOR (K D) ON '(-1
                                                                       -2
                                                                       -1 2
                                                                       1 -2
                                                                       1 2)
                                                        FOR Y = (+ K V)
                                                        FOR X = (+
                                                                 (OR D -1)
                                                                 H)
                                                        FOR U = (AND
                                                                 (< -1 Y N)
                                                                 (< -1 X N)
                                                                 (+ (* Y N)
                                                                    X))
                                                        IF U
                                                        DO #3=(IF (< #2# 4)
                                                                  (SETF #2#
                                                                          (LOGAND
                                                                           #2#
                                                                           O
                                                                           (IF (=
                                                                                W
                                                                                O)
                                                                               3
                                                                               0)))))
                                                  (DOTIMES (Y N)
                                                    (DOTIMES (X N)
                                                      (LET ((U
                                                             (+ (* Y N) X))
                                                            (O 6))
                                                        (IF (OR (= X H)
                                                                (= Y V)
                                                                (=
                                                                 (ABS
                                                                  (- H X))
                                                                 (ABS
                                                                  (- V
                                                                     Y))))
                                                            #3#))))
                                                  (SETF #2# W
                                                        R
                                                          (OR
                                                           (COND
                                                            ((= W 5)
                                                             (R 6 @ U B !))
                                                            ((R 5 @ U B
                                                                NIL)
                                                             T)
                                                            ((CATCH '!
                                                               (R 5 0 0 B
                                                                  T))
                                                             T)
                                                            (T
                                                             (AND
                                                              (= (DECF C)
                                                                 0)
                                                              (INCF D)
                                                              (OR
                                                               (FORMAT T
                                                                       "~%(lambda(&aux(n ~A)(d ~A))~%~S)"
                                                                       N D
                                                                       '#1#)
                                                               (THROW '$
                                                                 B)))
                                                             T))
                                                           R)))))
              R))
     (R 5 0 0 (FILL (MAKE-ARRAY S) 3) NIL)))))

另外,此函数返回的值为:

#(5 0 0 0 0 0 0 6 0 0 0 2 0 2 0 0)

...这是一个数组文字。数字5代表皇后,数字6代表骑士,其他任何数字都代表一个空单元,除非内部存储了更多信息。如果我们将返回的函数复制粘贴到repl,则会获得一个新函数。

#<FUNCTION (LAMBDA (&AUX (N 4) (D 2))) {100819148B}>

我们可以不带参数地调用它:

QN> (funcall * )

该调用返回一个新的解决方案#(5 0 0 0 0 0 0 2 0 0 0 6 0 0 2 0)以及另一个函数的来源(此处未显示)。如果原始函数或最后一个生成的函数找不到解决方案,则不打印任何内容,也不返回任何内容。

内部价值

|----------+--------+---------+--------+-----------------|
|          | Binary | Decimal | Symbol | Meaning         |
|----------+--------+---------+--------+-----------------|
| Empty    |    000 |       0 | -      | safe for none   |
|          |    001 |       1 | q      | safe for queen  |
|          |    010 |       2 | n      | safe for knight |
|          |    011 |       3 | #      | safe for both   |
|----------+--------+---------+--------+-----------------|
| Occupied |    101 |       5 | Q      | a queen         |
|          |    110 |       6 | K      | a knight        |
|----------+--------+---------+--------+-----------------|

我过去生成的解决方案太少了。现在,我将分别传播对女王和骑士安全的牢房。例如,以下是带有漂亮打印的n = 5的输出:

Q - - - - 
- - - n N 
- q - n n 
- # n - n 
- n # # - 

当我们放置女王/王后时Q,与女王/王后格格不入的位置对于女王来说仍然是安全的,并已标明q。同样,只有皇后才能到达的骑士对其他骑士来说是安全的。值按位 -ed表示可能的移动,并且有些单元格无法到达。

更准确地说,这是导致以下解决方案的木板顺序(从左到右),其中自由单元逐渐受到不同值的约束:

# # # # # #     q - - - q #     - - - - - #     - - - - - #     - - - - - n
# # # # # #     - - Q - - -     - - Q - - -     - - Q - - -     - - Q - - -
# # # # # #     q - - - q #     q - - - - -     Q - - - - -     Q - - - - -
# # # # # #     - q - q - #     - q - - - n     - - - - - n     - - - - - n
# # # # # #     # # - # # -     n n - n N -     - - - n N -     - - - - N -
# # # # # #     # # - # # #     # # - n n n     - # - - n n     - n - - n N

非奎因法

取消评论的版本

(defun queens-and-knights
    (n    ; size of problem
     fn   ; function called for each solution

     ;; AUX parameters are like LET* bindings but shorter.
     &aux
       ;; total number of cells in a board
       (s (* n n)))

  (labels
      ;; Define recursive function R
      ((R (w      ; what piece to place: 5=queen, 6=knight 
           %      ; min position for piece of type W
           @      ; min position for the other kind of piece
           b      ; current board
           !      ; T iff we are in "check" mode (see below)
           &aux  
           r      ; result of this function: will be "true" iff we can
                  ; place at least one piece of type W on the board b
           h      ; current horizontal position 
           v      ; current vertical position
           a      ; current piece at position (h,v)
           )

         (loop
            ;; only consider position U starting from position %,
            ;; because any other position below % was already visited
            ;; at a higher level of recursion (e.g. the second queen
            ;; we place is being placed in a recursive call, and we
            ;; don't visit position before the first queen).
            for u from % below s

            do
              (setf h (mod u n)         ; Intialize H, V and A
                    v (floor u n)       ; 
                    a (aref b u))       ; 

            ;; Apply an AND mask to current value A in the board
            ;; with the type of chess piece W. In order to consider
            ;; position U as "safe", the result of the bitwise AND
            ;; must be below 4 (empty cell) and non-null.
              (when (< 0 (logand a w) 4)

                ;; WE FOUND A SAFE PLACE TO PUT PIECE W

                (when (and ! (= 6 w))
                  ;; In "check" mode, when we place a knight, we knwo
                  ;; that the check is successful. In other words, it
                  ;; is possible to place an additional queen and
                  ;; knight in some board up the call stack. Instead
                  ;; of updating the board we can directly exit from
                  ;; here (that gave a major speed improvement since
                  ;; we do this a lot). Here we do a non-local exit to
                  ;; the catch named "!".
                  (throw '! t))

                ;; We make a copy of current board b and bind it to the
                ;; same symbol b. This allocates a lot of memory
                ;; compared to the previous approach where I used a
                ;; single board and an "undo" list, but it is shorter
                ;; both in code size and in runtime.
                (let ((b (copy-seq b)))

                  ;; Propagate knights' constraints
                  (loop
                     ;; O is the other kind of piece, i.e. queen here
                     ;; because be propagate knights. This is used as
                     ;; a mask to remove knights pieces as possible
                     ;; choices.
                     with o = 5

                     ;; The list below is arranged so that two
                     ;; consecutive numbers form a knight-move. The ON
                     ;; iteration keyword descend sublist by sublist,
                     ;; i.e. (-1 -2), (-2 -1), (-1 2), ..., (2 NIL). We
                     ;; destructure each list being iterated as (K D),
                     ;; and when D is NIL, we use value -1.
                     for (K D) on '(-1 -2 -1 2 1 -2 1 2)

                     ;; Compute position X, Y and index U in board,
                     ;; while checking that the position is inside the
                     ;; board.
                     for y = (+ K v)
                     for x = (+ (or D -1) h)
                     for u = (and (< -1 y n)
                                  (< -1 x n)
                                  (+(* y n)x))

                     ;; if U is a valid position...
                     if u
                     do
                     ;; The reader variable #1# is affected to the
                     ;; following expression and reused below for
                     ;; queens. That's why the expression is not
                     ;; specific to knights. The trick here is to
                     ;; use the symbols with different lexical
                     ;; bindings.
                       #1=(when (< (aref b u) 4) ; empty?
                            (setf (aref b u)

                                  (logand
                                   ;; Bitwise AND of current value ...
                                   (aref b u)

                                   ;; ... with o: position U is not a
                                   ;; safe place for W (inverse of O)
                                   ;; anymore, because if we put a W
                                   ;; there, it would attack our
                                   ;; current cell (H,V).
                                   o

                                   ;; ... and with zero (unsafe for
                                   ;; all) if our piece W is also a
                                   ;; knight (resp. queen). Indeed, we
                                   ;; cannot put anything at position
                                   ;; U because we are attacking it.
                                   (if (= w o) 3 0)))))

                  ;; Propagate queens' constraints
                  (dotimes (y N)
                    (dotimes (x N)
                      (let ((u(+(* y n)x))(o 6))
                        (if (or (= x h)
                                (= y v)
                                (= (abs(- h x)) (abs(- v y))))

                            ;; Same code as above #1=(if ...)
                            #1#))))

                  (setf
                   ;; Place piece
                   (aref b u) w

                   ;; Set result value
                   r (or (cond
                           ;; Queen? Try to place a Knight and maybe
                           ;; other queens. The result is true only if
                           ;; the recursive call is.
                           ((= w 5) (R 6 @ U b !))

                           ;; Not a queen, so all below concern   
                           ;; knights: we always return T because
                           ;; we found a safe position.
                           ;; But we still need to know if
                           ;; board B is an actual solution and 
                           ;; call FN if it is.
                           ;; ------------------------------------

                           ;; Can be place a queen too? then current
                           ;; board is not a solution.
                           ((R 5 @ U b()) t)

                           ;; Try to place a queen and a knight
                           ;; without constraining the min positions
                           ;; (% and @); this is the "check" mode that
                           ;; is represented by the last argument to
                           ;; R, set to T here. If it throws true,
                           ;; then board B is a duplicate of a
                           ;; previous one, except that it is missing
                           ;; pieces due to constraints % and @. The
                           ;; "check" mode is a fix to a bug where we
                           ;; reported as solutions boards where there
                           ;; was still room for other pieces.
                           ((catch'!(R 5 0 0 b t)) t)

                           ;; Default case: we could not add one more
                           ;; layer of pieces, and so current board B
                           ;; is a solution. Call function FN.
                           (t (funcall fn b) t))

                         ;; R keeps being true if it already was for
                         ;; another position.
                         r)))))

         ;; Return result R
         r))

    ;; Start search with a queen and an empty board.
    (R 5 0 0 (fill (make-array s) 3)  nil)))

重复和错误

我的第一个解决方案输出了重复的解决方案。为了解决这个问题,我为皇后和骑士介绍了两个柜台。皇后区(骑士)计数器跟踪板中存在皇后区(骑士)的第一个位置:我仅在跟随该最小位置的位置添加皇后区(骑士)。

这些方法使我无法再访问以前的迭代中已经找到的解决方案,因为我以增加的女王(或骑士)位置进行迭代。

但是,Sleafar注意到,存在一些解决方案,可能会为女王和骑士留出空间,这是违反规则的。尽管有一段时间,我还是不得不恢复正常的搜索并存储所有已知的解决方案,以防止重复,这会导致开销太大(在字节和内存使用方面)。

相反,这就是我现在要做的事情:找到潜在的解决方案板时,我尝试添加一个皇后和一个骑士,而不考虑计数器(即板上的所有单元)。如果可能的话,那么当前板是上一个板的副本,我拒绝该解决方案。

测验

|---+---------+------------+--------------|
| N |  boards |    seconds |        bytes |
|---+---------+------------+--------------|
| 3 |       0 |          0 |        32768 |
| 4 |      40 |          0 |       360416 |
| 5 |     172 |          0 |      3440016 |
| 6 |    2836 |   0.085907 |     61251584 |
| 7 |   23876 |   1.265178 |    869666288 |
| 8 |  383586 |  24.991300 |  17235142848 |
| 9 | 6064506 | 524.982987 | 359952648832 |
|---+---------+------------+--------------|

量化

我有不同的想法来制作连续的提子。最简单的方法可能是首先将所有解决方案生成为字符串列表,然后编写顺序生成的Quines,每一代从该列表中弹出。但是,这似乎并不比当前方法短。另外,我尝试用自定义堆栈重写递归代码,并在每次找到解决方案时都转储所有状态变量。目标是可以将下一步作为当前步骤的继续进行处理。也许这将更适合于基于堆栈的语言。当前的代码非常简单,并且依赖于Common Lisp读取器变量,使用它们总是很有趣。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.