微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

“生成数字”拼图

如何解决“生成数字”拼图

我遇到了以下难题,无法在 Picat 中制定解决方案:

您将生成 5 位数字,其中每个数字都在 1..5 中并且与其他数字不同,并且限制为一个数字中使用的任何三个相邻数字不能是在另一个号码中使用。 根据这个规则可以得到多少个不同的数字?

例如,如果我们生成数字12345,其他数字不能包含123345456,因此以下形式的所有数字都被禁止来自链:

123AB,A123B,AB123,234AB,A234B,AB234,345AB,A345B,AB345,

当我构建数字列表时,我对如何存储这些“禁止”子列表以及如何根据它们检查每个数字感到非常困惑。

我的尝试:

我想我设法为给定的链状态生成了有效的“候选”,但我不知道如何生成这样的链。

import cp.
import util.

valid(Ls,Cd) ?=>
    % verify that the head of the chain is correct?
    % so the chain consists of permutations of 12345
    foreach (L in Ls)
        len(L) = 5,permutation(L,[1,2,3,4,5])
    end,% generate the candidate
    Cd = new_list(5),permutation(Cd,5]),% check the candidate against the head of the chain
    foreach (L in Ls)
        not sublist([L[1],L[2],L[3]],Cd),not sublist([L[2],L[3],L[4]],not sublist([L[3],L[4],L[5]],Cd)
    end,solve(Ls),printf("Cd: %w\n",fail,nl.

% so that 3 element sublists of 12345 are 123,234 and 345.
sublist(X,S) =>
  append(_,T,S),append(X,_,T),X.len #>= 0.

% seems to work,the candidates don't have the banned triplets as sublists.
% so in this case the banned triplets would be
% 123,234,345,543,432,321
go => valid([[1,5],[5,1]],_).

main => go.

评论:情况不对称确实很有趣。如果我们分析状态:

[12345,12435,12534,13245,13425,13524,14235,14325,14523,21543,24153,25413,35421,43152]

我们看到有效/可以附加到此链的三个候选是:

Cd1: [5,1,4]
Cd2: [4,5,2]
Cd3: [4,1]

显然,如果我们选择 Cd3,因为它同时包含 453532,它不允许我们选择其后的任何候选者,因此链在 N=15 处结束。

如果我们选择Cd1,它排除了Cd3但仍然保留了Cd2,所以链继续到N=16

同样,如果我们选择 Cd2,它会排除 Cd3 但仍保留 Cd1,因此 N=16 也是可能的。

因此,似乎一般来说,某些候选者包含(因此排除)其他候选者,而链条的长度取决于我们是否选择这些候选者。

解决方法

这是带有 Update 4Update 5Update 6 中的模型的 Picat 模型:http://hakank.org/picat/generating_numbers.pi

更新 6:这可能是我会写的约束模型,如果不是从一开始就对问题的错误假设误入歧途......这是一种更直接的方法(从约束程序员的角度来看) ) 并且不要使用 permutations/1 等。

它比更新 5 稍慢(使用 sat 求解器为 3.7 秒,而更新 4 模型为 3.3 秒)。然而,cp 求解器在此模型上要慢得多。 在上面引用的 Picat 程序中,它的模型是 go3/0。 (最快的模型是 go/0。)

方法:

  • 创建一个域为 1..5 的 20 x 5 矩阵。
  • 对于每一行,确保它是不同的数字
  • 并在循环中确保没有常见的三元组

模型:

go3 ?=>
  nolog,N = 5,M = 20,X = new_array(M,N),X :: 1..N,% symmetry breaking
  X[1,1] #= 1,X[1,2] #= 2,3] #= 3,4] #= 4,5] #= 5,foreach(I in 1..M)
    all_distinct([X[I,K] : K in 1..N]),foreach(J in 1..I-1)
      foreach(A in 0..2)
        foreach(B in 0..2)
          sum([X[I,K+A] #= X[J,K+B] : K in 1..3]) #< 3
        end
     end
   end
 end,solve($[ff,split],X),foreach(P in X)
   println(P.to_list)
 end,println(numbers=[[I.to_string : I in  T].join('').to_int : T in X]),nl.
 go3 => true.

第一个解决方案(3.7 秒,sat):

 [12345,35421,23154,25314,43512,32415,32541,12453,21534,14523,34251,14235,54312,45132,51432,52134,53214,34125,41352,15243]
 

更新 5 这是一种更快的方法:大约 3.3 秒找到第一个解决方案,而更新 4 中的方法需要 1 分 25 秒。

这里的方法是:

  • 预处理步骤:从 120 个排列 (Ps) 中,构建一个 120 x 120 的矩阵 A 为 0/1,其中 A[P1,P2] = 1 表示 Ps[P1] 和 {{1 }} 是兼容的,即它们没有共同的三元组
  • 模型:创建一个长度为 120 的 0/1 列表 Ps[P2],其中 X 表示排列 X[I] = 1 应该在序列中(或者更确切地说是“设置”,因为顺序的排列没有区别)。
  • 在 foreach 循环中,Ps[I] 是一种“奇怪的”方式,表示 X[I]*X[J] #= 1 #=> A[I,J]X[I] 都应该在序列中,如果 X[J]

cp 求解器需要大约 3.3s 才能找到第一个长度为 20 的解。此模型的 sat 求解器较慢:4.8 秒(因此它仍然比 Update 4 版本快得多)。

这里是完整的模型:

A[I,J] #= 1

这是第一个解决方案:

go ?=>
  N = 5,Ps = permutations(1..N),PsLen = Ps.len,% Compatibility matrix: 
  % A[P1,P2] = 1 if they don't have any common triple
  A = new_array(PsLen,PsLen),bind_vars(A,0),foreach(P1 in 1..PsLen)
    A[P1,P1] := 1,foreach(P2 in 1..PsLen,P1 < P2)
      if check_perms(Ps[P1],Ps[P2]) then
        A[P1,P2] := 1,A[P2,P1] := 1
      end
    end 
 end,% length 20 sequence
 println(m=M),% List of 0/1: 
 % 1 means that it should be in the sequence
 X = new_list(PsLen),X :: 0..1,sum(X) #= M,% We want M 1s

 X[1] #= 1,% symmetry breaking
 foreach(I in 1..PsLen)
   foreach(J in 1..I-1)
     X[I]*X[J] #= 1 #=> A[I,J]
   end
 end,solve($[degree,updown],println(x=X),Perms = [Ps[I] : I in 1..PsLen,X[I]==1],foreach(P in Perms)
   println(P)
 end,println(numbers=[[I.to_string : I in  T].join('').to_int : T in Perms]),% println("Checking:"),% foreach(I in 1..Perms.len,J in 1..I-1)
 %    if not check_perms(Perms[I],Perms[J]) then
 %       println("ERROR!"=Perms[I]=Perms[J])
 %    end
 % end,nl,% fail,nl.
go4 => true.

% list version
check2(Forbidden,Tri) =>
  foreach(PP in Tri)
    not membchk(PP,Forbidden)
 end.

check_perms(Perm1,Perm2) =>
  tri(Perm1,Tri1),tri(Perm2,Tri2),foreach(PP in Tri2)
    not membchk(PP,Tri1)
  end,foreach(PP in Tri1)
    not membchk(PP,Tri2)
  end.

tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].

更新 4 正如评论中提到的,这是一个约束模型,它找到了一个长度为 20 的序列。

seq 为 20 是最佳的,理由如下:在 1..5 的 120 个排列的集合中,有 60 个可能的三元组。每个数字由 3 个独特的三元组组成。因此,在这样的序列中不能有超过 60 / 3 = 20 个数字。

这是一个 20 的数字序列:

x =  [1,1,1]
[1,2,3,4,5]
[3,5,1]
[2,5]
[4,2]
[2,3]
[4,4]
[3,4]
[5,2]
[1,3]
[5,1]

numbers = [12345,21435,43125,24513,42153,45231,14532,23541,13254,35124,31542,53412,15243,51423,54321]

CPU time 3.325 seconds. Backtracks: 233455

这个使用 sat 求解器的模型需要大约 1 分 25 分来开始这个序列。它比使用回溯的先前版本中“简单”使用列表处理要复杂一些,这就是这些方法中获得最大长度序列的问题。

一些评论:

  • [12345,32451,15423,41532,24135,14352,31524,54321,42513,51243,34215,35142,21453,13254] 用于连接 matrix_element/4 矩阵中的三元组和 Y 中的数字。
  • 三元组表示为数字 123..543(在 Z 中),因此我们可以确保它们是不同的。
  • 像往常一样,Picat 的 Z 模块在更简单的实例(例如长度高达 16)上速度更快,但对于更大的实例 (>16),cp 往往会好得多。

模型:

sat

而且我仍然认为有一些算法方法可以立即解决这个问题......

Update3 唉,Update2 中的程序仍然是错误的,因为它只选择了排列列表中后面的数字。第三个版本使用 import sat,util. go3 ?=> nolog,PLen = Ps.len,% Find the triplets TripletsMap = new_map(),foreach(P in Ps) tri(P,Tri),foreach(T in Tri) TripletsMap.put(T,1) end end,% Convert to numbers (123..543) Triplets = [T[1]*100+T[2]*10+T[3] : T in keys(TripletsMap)].sort,% length of sequence member(M,20..20),println(m=M),% Indices of the selected permutation X = new_list(M),X :: 1..PLen,% The triplets Z = new_list(M*3),Z :: Triplets,% Y contains the "shortcuts" to the permutations Y = new_array(M,5),Y :: 1..N,all_distinct(X),all_distinct(Z),X[1] #= 1,% symmetry breaking % Fill Y foreach(I in 1..M) element(I,X,II),foreach(K in 1..5) matrix_element(Ps,II,K,Y[I,K]) end end,% Convert triplet list in Y <-> triplet number in Z C = 1,foreach(I in 1..M) foreach(J in 1..3) to_num([Y[I,J+K] : K in 0..2],10,Z[C]),C := C+1 end end,Vars = Z ++ X ++ Y.vars,solve($[constr,updown,Vars) % split (SAT) PsX = [Ps[I] : I in X],println(numbers=[[I.to_string : I in Ps[T]].join('').to_int : T in X]),nl. go3 => true. tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3]. % converts a number Num to/from a list of integer % List given a base Base to_num(List,Base,Num) => Len = length(List),Num #= sum([List[I]*Base**(Len-I) : I in 1..Len]). ,因此所有数字都可以选择更改。

permutation(1..5,Next)

第一个解的长度为 16:

go2 ?=>
  Ps = permutations(1..5),Forbidden = [],gen(Ps,Forbidden,L),println([[I.to_string : I in  C].join('').to_int : C in L]),println(len=L.len),fail,nl.
go2 => true.

%
% Create triplets (Tri) from the permutation P
%
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].

% list version
check2(Forbidden,Forbidden)
  end.


% list version
add_forbidden_triplets2(Forbidden,Triplets) = F =>
  foreach(T in Triplets)
     Forbidden := Forbidden ++ [T]
  end,F = Forbidden.

gen([],_Forbidden,[]).
gen(Ps,[Next|L]) :-
   permutation(1..5,Next),not membchk(Next,tri(Next,check2(Forbidden,% Forbidden := add_forbidden_triplets2(Forbidden,Forbidden2 = add_forbidden_triplets2(Forbidden,% better
   Ps2 = [PP : PP in Ps,PP != Next],gen(Ps2,Forbidden2,L).
gen(_Ps,[]) :-
   not (permutation(1..5,Tri)).

下一个解决方案(通过回溯) - 然而 - 长度为 15:

  [12345,12435,12534,13245,13425,13524,14325,21543,24153,25413,43152,45312,53214]

所以我 - 仍然 - 不确定 16 是否是最大长度。

Update2Update 中的版本不完全正确(实际上是完全错误的),因为我忘记在 [12345,45321] 中添加三元组循环(Forbidden。程序更新如下。

第一个以 12345 开头的解决方案是:

add_forbidden_triplets(Forbidden,Triplets)

现在变得有趣了,因为其他序列的长度(具有不同的起始编号)大约是 12..17 个数字。这与直觉相反,因为这些东西应该是对称的,不是吗?

更新:由于我第一次在说明中遗漏了一个重要的约束,这里是一个基于第一种方法的调整程序。它产生一个长度为 107 的序列。基本的——也是非常简单的——变化是,禁止的三元组现在保存在哈希表 [12345,23145,45213,53214] len = 14 中。当没有任何可用数字时(当 Forbidden 为假)时,序列结束。

Found

这是第一个解决方案:

go ?=>
  N = 5,select(P,Ps,Ps2),L = [P],tri(P,Triplets),Forbidden = new_map(),% keep forbidden triplets in a hash table
  add_forbidden_triplets(Forbidden,% added in **Update2**
  Found = true,while(Found == true)
    if select(NextP,Ps2,Ps3),tri(NextP,PTri),check(Forbidden,PTri)    then
      L := L ++ [NextP],add_forbidden_triplets(Forbidden,P := NextP,Ps2 := Ps3
    else
      Found := false
    end
   end,% generate a new solution
   nl.
 go => true.

 %
 % Create triplets (Tri) from the permutation P
 %
 tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].

 %
 % Check if Tri contain some forbidden triplet
 %
 check(Forbidden,Tri) =>
   foreach(PP in Tri)
     not Forbidden.has_key(PP)
   end.


 %
 % Add triplets to Forbidden map
 %  
 add_forbidden_triplets(Forbidden,Triplets) =>
   foreach(T in Triplets)
     Forbidden.put(T,1)
   end.

这是我的原始答案

您的程序生成 106+1 个数字(使用初始数字为 12345),而不是我下面的程序生成的所有 120 个数字。也许我错过了问题中的某些要求?顺便说一下,您的程序中不需要 [12345,31245,32145,1425,31452,34152,24315,24351,42135,42315,42351,41325,43215,43251,43521,24531,14253,41253,42531,41523,45321,21354,23514,31254,32154,32514,35214,35241,13542,35412,25134,25341,52314,15324,51324,53124,53241,15342,51342,53142,53421,12543,25143,25431,52143,52413,52431,54213,54231,15432,54132,54321] len = 107 ,因为没有任何限制。

以下是我的两种方法:都生成长度为 120 的序列,即所有数字都可以“链接”。两者都使用 solve/1(来自 permutations/1 模块)首先生成所有 120 个排列(util)并不确定地选择剩余的一些排列(使用 5!=120 )。使用 select/3 生成所有三元组并使用 tri/2 来检查是否没有共同的三元组来检查允许的后继。

因为我很早就发现所有数字都可以使用(除非我遗漏了一些东西),所以程序完成时的控制是在没有可用排列的时候。这可能是我的方法的一个缺点。

check/2

这是 import util. % Using foreach loop go ?=> N = 5,% pick the first number (i.e. 12345) L := [P],while(Ps2 != []) tri(P,Forbidden),select(NextP,L := L ++ [NextP],Ps2 := Ps3 end,% convert to number nl. go => true. % Using genx/2 ("Prolog style") go3 ?=> Ps = permutations(1..5),println(plen=PLen),genx(Ps,nl. go3 => true. % Create triplets (Tri) from the permutation P tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3]. % Check if Tri contain some forbidden triplet check(Forbidden,Tri) => foreach(PP in Tri) not membchk(PP,Forbidden) end. % This is the same principal logic as used in go/0 % but in "Prolog style" genx([],[]). genx([P],[P]). genx([P|Ps],[P|L]) :- tri(P,select(Next,% pick a new available number tri(Next,genx([Next|Ps2],L). 的输出(转换为数字):

go/0

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?