用两种瓷砖尺寸建墙的方法数统计

9
你有一组积木,可以使用3英寸×1英寸和4.5英寸×1英寸的积木来搭建面板。为了保证结构完整性,相邻行之间的空隙不能对齐。有两种方法可以构建一个7.5英寸×1英寸的面板,2种方法可以构建一个7.5英寸×2英寸的面板,4种方法可以构建一个12英寸×3英寸的面板,以及7958种方法可以构建一个27英寸×5英寸的面板。有多少种不同的方法可以构建一个48英寸×10英寸的面板?

0(4.5 x 1 块)和 9(3 x 1 块)--> 9 C 0 = 1 种方式

1 种方式 + 35 种方式 + 28 种方式 + 1 种方式 = 65 种方式

如您所见,这里的方式数量远远不及 7958。我在这里做错了什么?

还有,我该如何找到构建一个 48 x 10 面板的方式数量?因为手动计算 7958 种方式有点困难。

如何编写程序来计算 7958 面板的方式数量?构建一个程序来计算结果会更容易吗?任何帮助都将不胜感激。


6
6个赞?你一定在开玩笑!非常可疑... - Mitch Wheat
1
@Mitch,这次提问者实际上已经付出了一些努力。他们还没有编写任何代码,因为他们不理解问题陈述,所以他们正在寻求帮助来理解它。 - Tyler
1
OP,看起来你只考虑了一行,因此得到的数字相对较小。对于一些较小的示例,恰好选择第一行的布局可以唯一确定上面一行的布局(通过间距属性),因此你得到了这些正确的数字。 - user837324
2
@Matrixfrog:好的,但是6个赞? - Mitch Wheat
2
我不确定为什么“选择”功能适用于这个问题,但是假设它确实适用,我认为你的计算是制作一个27x1面板的方法数。然后您可以以65种方式制作第二层,因此制作27x2面板的方式有65 ^ 2种。 除外,其中一些将无法正常工作,因为它们的砖块之间的分离彼此对齐。 - Tyler
1
@Mitch 我认为这是一个有趣的问题,而且它与语言无关(尽管没有标记为这样),这意味着它可能吸引任何人,不像大多数只吸引Java人或Python人或其他人的问题。 耸肩 - Tyler
5个回答

4
我认为“选择”函数不直接适用于您的“相邻行之间的块之间不能对齐”的要求。我还认为这是您的分析开始崩溃的地方:
面板:12 x 3面板=2种方式->2(4.5 x 1块)和1(3 x 1块)->3 C 1 = 3种方式;0(4.5 x 1块)和4(3 x 1块)->4 C 0 = 1种方式;3种方式+1种方式=4种方式 让我们建一些面板(1 |=1行,2 - =1列):
+---------------------------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+---------------------------+

+---------------------------+
|          |         |      |      
|          |         |      |      
|          |         |      |      
+---------------------------+

+---------------------------+
|      |          |         |            
|      |          |         |      
|      |          |         |      
+---------------------------+

+---------------------------+
|          |      |         |                  
|          |      |         |      
|          |      |         |      
+---------------------------+

在这里,我们可以看到有4种不同的基本行类型,但是它们都不是有效的面板(它们都违反了“块不能对齐”的规则)。但是我们可以使用这些行类型来创建多个面板:

+---------------------------+
|      |      |      |      |
|      |      |      |      |
|          |         |      |      
+---------------------------+

+---------------------------+
|      |      |      |      |
|      |      |      |      |
|      |          |         |      
+---------------------------+

+---------------------------+
|      |      |      |      |
|      |      |      |      |
|          |      |         |     
+---------------------------+

+---------------------------+
|          |         |      |      
|          |         |      |
|      |      |      |      |
+---------------------------+

+---------------------------+
|          |         |      |      
|          |         |      |
|      |          |         |
+---------------------------+

+---------------------------+
|          |         |      |      
|          |         |      |
|          |      |         |
+---------------------------+

...

但是,以上这些都是无效的。有效的12x3面板如下:
+---------------------------+
|      |      |      |      | 
|          |      |         |
|      |      |      |      |
+---------------------------+

+---------------------------+
|          |      |         |
|      |      |      |      |
|          |      |         |
+---------------------------+

+---------------------------+
|          |         |      |
|      |          |         |
|          |         |      |
+---------------------------+

+---------------------------+
|      |          |         |
|          |         |      |
|      |          |         |
+---------------------------+

实际上有四个,但在这种情况下,它只是巧合与使用“选择”功能得到的结果相匹配。就总面板配置而言,远不止四个。


3
  1. 找出所有形成给定宽度的单行方式。我称之为“行类型”。例如12x3:宽度为12的有4种行类型:(3 3 3 3)(4.5 4.5 3)(4.5 3 4.5)(3 4.5 4.5)。我会将它们表示为间隙列表。例如:(3 6 9)(4.5 9)(4.5 7.5)(3 7.5)
  2. 对于这些行类型中的每一个,找到哪些其他行类型可以放在其上面。
  3. 例如:

    a. 在(3 6 9)上适合(4.5 7.5)

    b. 在(4.5 9)上适合(3 7.5)

    c. 在(4.5 7.5)上适合(3 6 9)

    d. 在(3 7.5)上适合(4.5 9)

  4. 枚举使用这些规则建立给定高度的堆栈的方法。动态规划适用于此,因为在每个级别上,您只需要知道最后一行的类型和到达该行类型的方法数。
编辑:我在咖啡时间试了一下,它可以工作。顺便说一句,48x10的解决方案有15个小数位。 编辑:以下是动态规划部分的更多细节:

步骤2中的规则转换为可能邻居的数组。数组的每个元素对应于一个行类型,并保存该行类型可能相邻的行类型的索引。

0: (2)
1: (3)
2: (0)
3: (1)

在12×3的情况下,每行类型只有一个可能的相邻行类型,但通常情况下可能会更多。动态规划始于单个行,其中每个行类型恰好有一种出现方式。
1 1 1 1

接下来的一行是通过为每种行类型添加可能的相邻行在前一行上形成的方式数量来形成的。对于宽度为12的情况,结果再次是1 1 1 1。最后,只需将最后一行相加即可。

复杂度:

  • 找到行类型对应于枚举树的叶子节点;这棵树有大约(/ width 3)层,因此这需要O(2w/3) = O(2w)的时间。

  • 检查两个行类型是否适合需要花费与它们的长度成比例的时间,O(w/3)。建立交叉表与行类型的数量的平方成比例。这使得第二步为O(w/3·22w/3) = O(2w)

  • 动态规划需要行数乘以行类型的数量乘以平均相邻数(我估计与行类型的对数成正比),O(h·2w/3·w/3) = O(2w)

正如您所看到的,这全部都被行类型的数量所主导,而这些随着宽度的增加呈指数增长。幸运的是,常数因子相当低,因此48×10可以在几秒钟内解决。


1

这看起来像是一种可以通过递归解决的问题。以下是一个算法的简要概述,其中包含一个接受前一层和剩余层数作为参数的递归方法:

  • 从初始层数(例如27x5从remainingLayers = 5开始)和一个空的前一层开始
  • 测试当前层的所有可能布局
    • 尝试在正在构建的层中的下一个可用插槽中添加3x1。检查它是否超过了目标宽度(例如,在27x5中不超过27宽度),并且它是否违反了给定前一层的间距条件
    • 继续尝试向当前层添加3x1,直到我们构建出一个确切宽度为27个单位的有效层
    • 如果我们无法在当前插槽中使用3x1,则将其删除并替换为4.5x1
    • 一旦我们有了有效的层,就将remainingLayers减少,并将其与我们刚刚构建的层一起传回我们的递归算法中
  • 一旦我们达到remainingLayers = 0,我们就构建了一个有效的面板,因此增加我们的计数器

我们的想法是构建所有可能的有效层的组合。一旦我们在27x5的示例中有了5个有效层叠加在一起,我们就构建了一个完整的有效面板。因此,算法应该找到(并因此计算)每个可能的有效面板,确保只计算一次。


听起来这似乎是一个不错的Prolog程序... - Tyler
@HappyPixel,我理解你的答案所涉及的概念,这绝对是一个很好的解决方案。接受2层的递归是个好主意。但是你能否详细说明一下?你能提供一些伪代码,以便我更清晰地理解这个概念吗?你会如何构建这些层? - MK1
@MK1 我正在编写一些样例Java代码,等我调试好了就会发布。 - user837324
@MK1发布了另一个带有Java代码的答案,似乎给出了27x5的正确答案。 - user837324
@HappyPixel,这绝对让事情更加清晰了。谢谢。 - MK1

1
这是一个“二维装箱”问题。有一些数学知识的人可以帮忙,或者你可以尝试一本关于计算算法的书。它被称为“组合 NP-难问题”。我不知道这意味着什么,但“难”这个词引起了我的注意 :)
我已经看过钢材切割程序,它们大多使用最佳猜测。但在这种情况下,2 x 4.5英寸垂直堆叠可以容纳3 x 3英寸水平堆叠。你可能可以避免浪费。当你必须找出最佳解决方案——最小浪费的方案时,情况会变得非常棘手。

由于限制的存在,这并不困难。 - Svante

0

这里是Java的解决方案,数组长度检查等有些凌乱,但我相信你可以轻松地优化它。

无论如何,我希望这能帮助你展示算法的工作原理 :-)

import java.util.Arrays;

public class Puzzle
{
    // Initial solve call
    public static int solve(int width, int height)
    {
        // Double the widths so we can use integers (6x1 and 9x1)
        int[] prev = {-1};      // Make sure we don't get any collisions on the first layer
        return solve(prev, new int[0], width * 2, height);
    }

    // Build the current layer recursively given the previous layer and the current layer
    private static int solve(int[] prev, int[] current, int width, int remaining)
    {
        // Check whether we have a valid frame
        if(remaining == 0)
            return 1;

        if(current.length > 0)
        {
            // Check for overflows
            if(current[current.length - 1] > width)
                return 0;

            // Check for aligned gaps
            for(int i = 0; i < prev.length; i++)
                if(prev[i] < width)
                    if(current[current.length - 1] == prev[i])
                        return 0;

            // If we have a complete valid layer
            if(current[current.length - 1] == width)
                return solve(current, new int[0], width, remaining - 1);
        }

        // Try adding a 6x1
        int total = 0;
        int[] newCurrent = Arrays.copyOf(current, current.length + 1);
        if(current.length > 0)
            newCurrent[newCurrent.length - 1] = current[current.length - 1] + 6;
        else
            newCurrent[0] = 6;
        total += solve(prev, newCurrent, width, remaining);

        // Try adding a 9x1
        if(current.length > 0)
            newCurrent[newCurrent.length - 1] = current[current.length - 1] + 9;
        else
            newCurrent[0] = 9;
        total += solve(prev, newCurrent, width, remaining);

        return total;
    }

    // Main method
    public static void main(String[] args)
    {
        // e.g. 27x5, outputs 7958
        System.out.println(Puzzle.solve(27, 5));
    }
}

@Svante 是的,基本上是这样。在每次调用solve时,它会沿着“3x1”分支尽可能远地前进,如果卡住了就回溯。然后它会对“4.5x1”分支做同样的事情。由于间隙条件使问题变得非常复杂,我想不出任何合理的组合方法来解决这个问题。 - user837324
2
为什么我被踩了?我给出了一个完全有效的答案,回答了楼主的问题...我从未声称我的方法超级高效或其他什么,我只是在展示一个递归解决方案。 - user837324
另一个问题是对于48x10根本不起作用,因为解决方案有15个小数位。即使你等待它需要的无数岁月,由于静默溢出,结果也将是错误的。 - Svante
1
再次强调,这只是为了演示目的(以说明我在另一个答案中描述的算法)。我本可以用伪代码编写它,但我觉得用Java更容易写。当这只是一个快速而简单的演示时,我不明白为什么你如此热衷于挑毛病...甚至OP在我的其他答案中留言说他发现它很有帮助。 - user837324
1
@Svante - 那么为什么不发布您的工作解决方案的代码,以便我们都可以从中学习呢? - aroth
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接