使用Python计算3 x n方格内用2 x 1骨牌填充的方法数量

使用Python计算3 x n方格内用2 x 1骨牌填充的方法数量

更多Python相关文章,请阅读:Python 教程

问题描述

给定一个3 x n的方格,现在需要我们用2 x 1的骨牌将其填满,求有多少种方法可以填充。

问题分析

首先我们可以得到一下观察结果:

当n = 1时,只能用两个小方格来填充,无解;

当n = 2时,只有一种方法,用两个小方格组成一块2 x 1的大方格并填入;

当n = 3时,总方法数为2,分别是:

  • 将3 x 2的方格填满,剩余一个1 x 1的小方格处于左上角;
  • 将3 x 2的方格填满,剩下的1 x 1的小方格处于右上角。

我们发现,对于n > 2的情况,我们可以将3 x n的方格划分为两部分:

  • 一个2 x n的方格;
  • 两个3 x n-2的方格。

现在考虑如何将骨牌放入其中。我们可以分三种情况来考虑。

情况一

将一块2 x 1的大方格垂直放入2 x n的方格中,如下所示:

|-------|   |-------|   |-------|
|o****o |   | ooo o |   | o   o |
|o****o |   | ooo o |   | o   o |
|-------|   | ooo o |   |ooooo o|
            |-------|   |ooooo o|

在这个情况下,我们可以重新定义一个2 x (n-1)的方格,再次进行划分。

情况二

将两块2 x 1的大方格水平放入2 x n的方格中,如下所示:

|-------|   |-------|
|o****o o|   | ooo o |
|o****o o|   |ooooo o|
|-------|   |ooooo o|

在情况二中,我们可以重新定义一个2 x (n-2)的方格,再次划分。

情况三

如果将两块2 x 1的大方格分别放入两个3 x n-2的方格中,我们可以得到两个新的3 x n-2的方格,如下所示:

----------*  *----------

|-----------|   |-----------|
| o  o  ooo o|   |ooooooooo o|
| o  o  ooo o|   |ooooooooo o|
| o  o  ooo o|   |ooooooooo o|
|-----------|   |-----------|

----------*  *----------

|-----------|   |-----------|
| ooo o  o  o|   |o ooooooooo|
| ooo o  o  o|   |o ooooooooo|
| ooo o  o  o|   |o ooooooooo|
|-----------|   |-----------|

在情况三中,我们需要考虑分别放入左右两边的大方格究竟是哪一种情况,也就是说我们需要将情况三再次细分。

现在我们已经能够将问题进行具体化,根据不同情况再次进行划分即可得到计算方法。

推导计算公式

我们令f(n)表示3 x n的方格用2 x 1的骨牌填充的不同方案数。对于两种边角的特殊情况n = 1和n = 2,可以直接得出:f(1) = 0,f(2) = 1 对于n > 2的情况,可以使用上一节中的方法进行递归推导。

情况一

在2 x n的方格中,我们使用一块2 x 1的大方格垂直放置,得到以下图形:

|-------|
|o****o |
|o****o |
|-------|

我们可以将这个方格转化为一个2 x (n-1)的方格,再次进行划分。此时,该方格有两个空位置:

|--|--|
|**| o|
|**| o|
|--|--|

第1个空位置:

  • 插入一个2 x 1的小方格,则剩下的部分需要使用2 x (n-2)的方格填满,方案数为f(n-2);
  • 插入一个1 x 2的小方格,则必须将其与原来的2 x 1的大方格组成一个2 x 2的大方格,并插入对应位置,剩下的部分需要使用2 x (n-3)的方格填满,方案数为f(n-3)。

第2个空位置同理,这样我们就推导出了情况一的公式:

f(n) = f(n-1) + f(n-2) + 2\sum\limits_{i=3}^{n}f(n-i)

情况二

在2 x n的方格中,我们使用两块2 x 1的大方格水平放置,如下所示:

|--------|
|o****o o|
|o****o o|
|--------|

此时,我们可以定义一个2 x (n-2)的方格,再次进行划分。这个方格有一个空位,插入一个2 x 1的小方格,那么剩下的部分需要使用2 x (n-4)的方格填满,方案数为f(n-4)。还有一种情况,需要将空位插入一个1 x 2的小方格,那么必须将其与原来的2 x 1的大方格组成一个2 x 2的大方格,并插入对应位置,剩下的部分需要使用2 x (n-5)的方格填满,方案数为f(n-5)。最终的公式为:

f(n) = f(n-2) + 2\sum\limits_{i=4}^{n}f(n-i)

情况三

对于情况三,需要进一步细分:

  • 将一个2 x 1的大方格插入到左边,另一个插入到右边;
  • 将一个2 x 1的大方格插入到左边,再将一个1 x 2的小方格插入到右边;
  • 将一个1 x 2的小方格插入到左边,再将一个2 x 1的大方格插入到右边;
  • 将一个1 x 2的小方格插入到左边,另一个插入到右边。

对于第一种情况,我们将左右两部分分别定义为一个3 x (n-2)的方格,再次进行划分。对于第二种情况,我们需要使用3 x (n-3)的方格填满后,再在左边使用一个2 x 1的大方格,右边使用一个2 x 2的大方格;对于第三种情况,我们需要使用3 x (n-3)的方格填满左边,再在右边使用一个2 x 1的大方格;对于第四种情况,同样将左右两边分别定义为3 x (n-2)的方格,继续进行划分。

最终的推导公式为:

f(n) = 2f(n-2) + f(n-4) + 2\sum\limits_{i=5}^{n}f(n-i)

Python实现

根据上述推导公式,我们可以使用递归 + 动态规划的方式进行求解。

def calc_methods(n, memo):
    if n == 1:
        return 0
    elif n == 2:
        return 1
    elif memo[n] is not None:
        return memo[n]
    else:
        memo[n] = calc_methods(n-1, memo) + calc_methods(n-2, memo)
        for i in range(3, n+1):
            memo[n] += 2 * calc_methods(n-i, memo)
        if n % 2 == 0:
            memo[n] += 2
        else:
            memo[n] += calc_methods((n-1)//2, memo) + calc_methods((n+1)//2, memo)
        return memo[n]

n = int(input("请输入一个正整数n:"))
memo = [None for _ in range(n+1)]
print("共有{}种方法可以填满3 x {}的方格".format(calc_methods(n, memo), n))

在上述代码中,我们使用了一个memo数组来记录每个n对应的方法数,避免重复计算。另外,由于第三种情况需要进行奇偶性判断,因此我们使用了三元运算符对奇偶情况进行判断。这样,我们就可以在O(n)的时间复杂度下解决该问题。

结论

本文中,我们使用递归 + 动态规划的方式,推导出了3 x n方格用2 x 1骨牌填充的方法数量计算公式。并编写了Python程序来解决该问题。通过本文的学习,读者可以了解到如何使用递归和动态规划的方式,来解决类似的组合计数问题。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程