【问题标题】:Counting the ways to build a wall with two tile sizes [closed]计算用两种瓷砖尺寸建造墙壁的方法[关闭]
【发布时间】:2011-07-11 03:22:20
【问题描述】:

给你一组积木,用 3”×1” 和 4.5”×1” 积木搭建一个面板。

为了结构完整性,块之间的空间不得在相邻行中排列。

7.5”×1”面板有2种方法,7.5”×2”面板有2种方法,12”×3”面板有4种方法,27”有7958种方法“×5”面板。构建 48”×10” 面板有多少种不同的方法?

这是我目前所理解的:

3 x 14.5 x 1

我已经使用组合公式来找到两个块可以排列在这种大小的面板中的所有可能组合

C = 选择 --> C(n, k) = n!/r!(n-r)!一次在 r 处组合 n 组

面板:7.5 x 1 = 2 种方式 -->

1(3 x 1 块)和 1(4.5 x 1 块)--> 仅使用 2 个块--> 2 C 1 = 2 种方式

面板:7.5 x 2 = 2 种方式

我在这里也使用了组合

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

面板: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 种方式

(这就是我感到困惑的地方)

面板 27 x 5 面板 = 7958 种方式

6(4.5 x 1 块) 和 0(3 x 1) --> 6 C 0 = 1 路

4(4.5 x 1 块)和 3(3 x 1 块)--> 7 C 3 = 35 路

2(4.5 x 1 块)和 6(3 x 1 块)--> 8 C 2 = 28 路

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 票?你开玩笑的吧!很狡猾...
  • @Mitch,除了这一次,提问者实际上表现出了一些努力。他们还没有编写任何代码,因为他们不理解问题陈述,所以他们寻求帮助以理解它。
  • OP,看起来您一次只考虑一行,因此您获得的数字相对较小。碰巧的是,对于一些较小的示例,选择第一行的布局唯一地确定了上面行的布局(通过间距属性),因此您得到了正确的数字。
  • @Matrixfrog:好的,但是有 6 个赞?
  • 我不确定为什么“选择”功能适合这个问题,但假设它是正确的,我认为您的计算是制作 27x1 面板的方法数。然后你可以用 65 种方式制作第二层,所以有 65^2 种方式制作 27x2 面板。 除了它们中的一些不起作用,因为它们会将砖块之间的间隔排成一列。

标签: algorithm combinations combinatorics tiling


【解决方案1】:

鉴于您的“块之间的空间不得在相邻行中排列”要求,我认为“选择”功能不直接适用。我也认为这是您的分析开始崩溃的地方:

面板: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 个-'s = 1 列):

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

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

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

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

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

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

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

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

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

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

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

...

但同样,这些都不是有效的。有效的 12x3 面板是:

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

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

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

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

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

【讨论】:

    【解决方案2】:
    1. 找到所有方法来形成给定宽度的单行。我称之为“行类型”。 示例 12x3:有 4 种宽度为 12 的行类型:(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 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)

    3. 根据这些规则列举出给定高度的堆叠方式。动态规划适用于此,因为在每个级别,您只需要最后一行类型和到达那里的方式数。

    编辑:我刚刚在喝咖啡的时候尝试了这个,它确实有效。顺便说一下,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)。建立交叉表与行类型数的平方成正比。这使得步骤 2 O(w/3·22w/3) = O(2w)

    • 动态编程需要 height 乘以行类型数乘以平均邻居数(我估计它是行类型数的对数),O(h·2 w/3·w/3) = O(2w).

    如您所见,这完全取决于行类型的数量,这些数量随宽度呈指数增长。好在常数因子比较小,48×10可以在几秒内解出来。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 听起来它会是一个很好的 Prolog 程序......
      • @HappyPixel 我理解您的答案所包含的概念,而且这绝对是一个很好的解决方案。接受 2 层的递归是个好主意。但你能否进一步说明这一点。您能否提供一些伪代码,以便我更清楚地了解这个概念?您将如何构建这些层?
      • @MK1 我正在为此编写一些示例 Java 代码,我会在它工作时发布它
      • @HappyPixel 谢谢,那太好了。
      • @MK1 用 Ja​​va 代码发布了另一个答案,似乎给出了 27x5 的正确答案
      【解决方案4】:

      这是一个“二维装箱”问题。具有良好数学知识的人将能够提供帮助,或者您可以尝试一本关于计算算法的书。它被称为“组合 NP 难题”。我不知道这意味着什么,但“硬”部分引起了我的注意:)

      我看过钢材切割程序,它们大多使用最佳猜测。在这种情况下,虽然 2 x 4.5" 垂直堆叠可以容纳 3 x 3" 英寸水平堆叠。你可能没有浪费就逃脱了。当您必须找出最佳解决方案时会变得相当棘手——浪费最少的解决方案。

      【讨论】:

      • 由于限制,并不难。
      【解决方案5】:

      这是一个 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 是的,差不多。在每次求解调用中,它会沿着“3x1”分支尽可能远,如果卡住则回溯。然后它对“4.5x1”分支执行相同的操作。我想不出任何合理的方法来组合解决这个问题,因为间隙条件使它非常复杂。
      • 为什么我被否决了?我对 OP 的问题给出了一个完全有效的答案......我从未声称我的方法是超级有效的或任何东西,我只是展示了该问题的递归解决方案。
      • 再次,这只是为了演示目的(说明我在其他答案中描述的算法)。我本可以用伪代码编写它,我只是发现编写 Java 更容易。我不明白为什么你如此热衷于在这个解决方案中挑选漏洞,而它只是一个快速而肮脏的演示...... OP 甚至说如果你在我的其他答案中看到 cmets,他会发现它很有帮助.
      • @Svante - 那么为什么不发布您的工作解决方案的代码,以便我们都可以从中学习呢?
      猜你喜欢
      • 2017-03-23
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 2011-05-25
      • 1970-01-01
      • 2013-01-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多