很高兴看到这个问题有两票。我现在要重新提出我的问题以避免混淆。
问题是如何用没有孔的随机但预定义的形状填充mxn网格/矩阵。预定义的形状具有变量k,其是形状由多少个块组成。每个块都是正方形,并且与网格方块的大小相同(即1x1网格)。可以旋转形状以适合网格,但不会收缩或扩展。 k不会改变一轮,换句话说,m,n和k在我运行答案脚本时不会改变。当我第二次运行脚本时,我可能会更改其中的一个或全部。例如,第一次,我可以运行答案脚本,其中k = 4,m = 10,n = 20。脚本完成并打印输出。第二次我将k = 3,m = 6且n = 10。我将保证m次n并且乘积调制k等于零(m x n%k = 0)以确保它们在数学上彼此适合。好的,还有一个条件:1
脚本需要从预设k池中随机填充网格。当k = 2时,预定义的形状只有一种,两个块在一起。如果你认为没有旋转,那么它有两种,水平和垂直。当k = 4时,基本上用Tetris块填充网格,即共有7种预定义形状(每种形状都可以旋转,并且可以制作~20种)。什么是k = 5的预定义形状,我还不知道。答案可以计算或者可以硬编码,因为找到k = 5的所有形状并不困难。
如果解决方案有限,则不需要随机。例如,m = 2,n = 2,k = 4;或m = 1,n = 4,k = 2。别无他法,没有随机。
网格中的任何位置都不能留任何洞。我认为,没有经过深思熟虑,许多使用mxn和mxn%k = 0的网格将有一个没有洞的解决方案。直观地说这听起来很合理,但在数学上我不知道。如果m或n是k的倍数,则保证有解(所有直条)。
理想情况下,我希望k是一个小整数,如k <10,但在2到5的范围内是可以接受的。如果它更简单,我们可以在这里有一个固定的k,例如4,因为俄罗斯方块有着名的7个形状(ITOLJSZ)。
我正在寻找Perl中的解决方案。 Python也没问题。程序每次都需要运行m,n和k。同样,我将m,n,k拟合mxn%k = 0.
我自己的努力,我在Perl中尝试过,可以解决k = 3的一些情况,并且由于边缘/角落中的单例(孔)而导致某些情况失败。需要一个好方法来检查是否有任何块变成单例。我的文本输出看起来像这样(m = 4,n = 9,k = 3)。你当然可以使用这种或任何有意义的格式。
AABB
ACCB
DCEE
DFFE
DFGH
IGGH
IIJH
KKJJ
KLLL
我将获得100分的奖励(感谢那两个投票,我现在有107个声誉)来奖励最佳解决方案。
感谢您的意见。
答案 0 :(得分:0)
以下是关于解决方案设计的一些驱动思路:
如果您不必放置每一件,您可以简单地跳过,直到您只有一堆直线或正方形。这个算法很容易想出来 - 弄清楚一个部分的配置来填充1或2行并重复它。如果(nm mod k!= 0)则没有解;否则,我怀疑你可以制定一套通用的规则。例如,如果(m mod k = 0)或(n mod k = 0)那么你可以使用直线。考虑这些会很有趣,但我会留给你。
实际上,阅读你的问题,我看到你写了2&lt; = k&lt; = 5 - 然后这很容易,因为2,3和5是素数。数论给我们如果nm mod一个素数p = 0那么n或m必须可以被p整除,所以对于k = 2,3,5,你可以找到n,m中哪一个可被k整除,并填充行或用直线长度为k的列。对于k = 4,n,m中的任何一个可被4整除(在这种情况下,您只使用相同的策略)或者它们都可以被2整除,在这种情况下,一个必须是(4x + 2)并且您只需填充每列用直线然后在最后放置正方形。
如果你必须放置你给的每件作品,那么你从一开始就知道你必须填充箱子的(nm / k)件。这为您提供了一个标准的bin-packing问题案例,它是NP-hard,但是有很好的基于启发式的算法。一个常见的是将每个形状放置在它进入的第一个开放位置的贪婪启发式。
然而,你的问题需要一个确切的解决方案,这意味着“足够接近”将永远不够好。您可以使用回溯算法,但更好的方法可能是对网格的有效位置的状态空间进行双向搜索。将一个位置定义为目标位置,然后从它向后移动,这涉及从随机(不是真的 - 你应该找到良好的启发式)位置取出碎片。然后将另一个位置定义为起始位置,并向前移动涉及插入碎片的位置。当两棵树相交并沿着那条路走时停下来。
你必须要处理的一个问题是,有时你不可能用你给出的碎片填满网格。例如,如果你有am = 2,n = 2,k = 4,你只得到一块,如果它是除了正方形以外的任何一块,你将无法填满状态空间。 / p>
答案 1 :(得分:0)
这个问题肯定是NP难的(它类似于背包),这意味着一般来说唯一解决它的方法是检查所有可行块放置的算法。这将需要类似分支和边界搜索的东西,它们以与拼图游戏相同的方式组装碎片,除了每个拼图块有任意多个副本。每当你在没有引入洞的情况下到达没有任何部分适合的点时,回溯。在伪代码中:
place one piece to make the initial blob B containing no holes
procedure branch_and_bound
loop
for each shape S
for each position P that S can be added
to B without creating an unfillable hole
place S in P to enlarge B
if puzzle is complete throw DONE!
call branch_and_bound
听起来你专注于如何找到位置P.一个简单的方法就是考虑每个接触B的空格子方格E并尝试将每个构成S的方格放在E中,抛弃所有引起的情况与B重叠。避免不可填充的孔可以简单地通过要求由填充的空间保持连续:不要让未填充空间的“岛”发展。通过挑选任何空方块,使用DFS或BFS触摸所有可能未填充的空间,然后检查剩余的未填充的未填充方块,可以轻松检测到岛屿。
您可以使用启发式方法来指导选择形状和放置部件的顺序。
事实上,除非启发式方法非常好或者块的形状(或两者都是),否则这种算法很快就会变得无用。这是NP难题的本质。即使启发式算法通常非常出色,也一定会出现他们悲惨失败的问题。这也是NP难题的本质。
许多拼图解算器中使用的技术是通过仅考虑有限的解决方案集来降低复杂性。这里的一个例子是如果m = Ap并且n = Bp,对于一些A整数A,B,p,则显然足以找到p×p平方的解。这可以平铺以获得更大的解决方案。你可以想象一下类似的想法。
答案 2 :(得分:0)
关于洞问题,应该有一个简单的方法来解决这个问题...... 3! (以及k <= 9即是;))
让我解释一下,每个区块不仅与另一个区块相邻,而且还与EMPTY SPOTS
相邻,关于k的形状,这是你应该关心的,每个这样的空点的计数总是k*2+2
所以k=1 -> 4, k=2 -> 6 .... k=9 -> 20
对于k.length的每个项目,你知道它的位置是正确的吗?在你的矩阵中,你现在可以向一边走一步并检查它是否是一个空的空间,如果是这样的话。请注意暂时(空白)位置,如果你用这样的方式勾勒出你的k,你会有一个不同数量的空白区域小于或等于上述计算。
如果相等,你不能在k中有任何漏洞,你可以继续, 如果更少,那么检查多少少,如果你错过了一个你做了L形,如果你错过TWO你要么是U形或T形(特殊情况k = 9和S),你的好去
如果您错过了三个或更多,您需要进一步调查! 现在检查你为它的相邻块临时记录的每个空白位置,如果每个空白的数字小于或等于3,你没有一个洞并且可以继续,一旦多于一个空白有3个相邻的块但是,你将在k中遇到一个漏洞。
对于k = 10来说很难做到这一点,但是这些简单的规则最多可以防止k对象出现漏洞。
抱歉,我不熟悉perl,否则我给你的代码不仅仅是一个提示;)
因此,如果您的任何k物体没有任何洞,您应该放心,您可以解决任何m*n%k=0
网格。你应该始终牢记一件事,那就是经典的多米诺骨牌问题*所以总是从一个角落/边缘开始填充你的网格,并且在你将最后一块插入网格之前不要留下任何空白,以避免这些情况。