126. 长方体层(Cuboid Layers)

在一个 \(3 \times 2 \times 1\) 的长方体上,覆盖每一个可见面所需的最少立方体数量是 \(22\)

如果我们在这基础上添加第二层,则需要 \(46\) 个立方体来覆盖所有可见面;第三层需要 \(78\) 个立方体;第四层需要 \(118\) 个立方体。

然而,在 \(5 \times 1 \times 1\) 的长方体上,第一层也需要 \(22\) 个立方体;同样地,在 \(5 \times 3 \times 1\)\(7 \times 2 \times 1\)\(11 \times 1 \times 1\) 的长方体上,第一层都需要 \(46\) 个立方体。

我们定义 \(C(n)\) 为"在某一层中恰好包含 \(n\) 个立方体"的长方体个数。因此 \(C(22) = 2\)\(C(46) = 4\)\(C(78) = 5\)\(C(118) = 8\)

事实证明,\(154\) 是使得 \(C(n) = 10\) 的最小的 \(n\)

求使得 \(C(n) = 1000\) 的最小的 \(n\)

一、数学背景

考虑一个尺寸为 \(a \times b \times c\)\(a \le b \le c\))的长方体。初始时,其外表面由 \(2(ab + ac + bc)\) 个单位正方形组成。第一层在每一个暴露的面上放置一个单位立方体,因此第一层恰好需要 \(2(ab + ac + bc)\) 个立方体。

经过 \(k-1\) 层之后,立体形状的表面面积(以单位正方形计)具有封闭形式。第 \(k\) 层所需的立方体数量为:

$$L(a,b,c,k) = 2(ab + bc + ac) + 4(k-1)(a+b+c) + 4(k-1)(k-2)$$

公式验证(以 \(3 \times 2 \times 1\) 为例):

对于固定的 \((a,b,c)\),相邻层之间的增量构成等差数列:

$$\Delta_k = L(a,b,c,k) - L(a,b,c,k-1) = 4(a+b+c) + 8(k-2)$$

首项(\(k=2\) 相对于 \(k=1\))为 \(4(a+b+c)\),公差为 \(8\)

二、算法设计

采用直接枚举法

  1. 迭代所有合理的 \((a,b,c,k)\) 组合,计算 \(L(a,b,c,k)\),统计每个 \(n\) 出现的次数 \(C(n)\)
  2. 从小到大检查 \(C(n)\),找到第一个等于 \(1000\)\(n\)

枚举边界

采用迭代加深策略:从 \(N=5000\) 开始,每次翻倍,直到找到答案。

三、复杂度分析

四、代码实现与说明

"""Project Euler 126 - Cuboid Layers."""

from time import perf_counter


def count_layers(max_n: int):
    """Count C(n) for all n from 1 to max_n via direct enumeration.

    Iterates over a <= b <= c and k >= 1, computing L(a,b,c,k) and
    incrementing the count for that value.

    Args:
        max_n: Maximum layer-cube count to consider.

    Returns:
        List C of length max_n+1 where C[n] = count of (a,b,c,k) producing n.
    """
    C = [0] * (max_n + 1)

    # a <= sqrt(max_n / 2) because 2ab <= L for any k >= 1, b >= a
    max_a = int((max_n / 2) ** 0.5) + 1

    for a in range(1, max_a + 1):
        a2 = a * a
        half_n = max_n / 2

        # For fixed a, minimum L occurs when b=c and k=1:
        #   L_min = 2ab + 2b(a+b) = 4ab + 2b^2 <= max_n
        #   => b <= -a + sqrt(a^2 + max_n/2)
        max_b = int(-a + (a2 + half_n) ** 0.5) + 1
        if max_b < a:
            continue

        for b in range(a, max_b + 1):
            ab = a * b
            s_ab = a + b  # a + b

            # For k=1: L = 2ab + 2c(a+b) <= max_n
            #   => c <= (max_n/2 - ab) / (a+b)
            max_c = (max_n // 2 - ab) // s_ab
            if max_c < b:
                continue

            for c in range(b, max_c + 1):
                s_abc = s_ab + c  # a + b + c

                # L for k=1
                L = 2 * (ab + b * c + a * c)
                if L > max_n:
                    continue
                C[L] += 1

                # For k >= 2: L(k) = L(k-1) + 4(a+b+c) + 8(k-2)
                # First increment (k=1 -> k=2): inc = 4(a+b+c)
                inc = 4 * s_abc
                while True:
                    L += inc
                    if L > max_n:
                        break
                    C[L] += 1
                    inc += 8  # each subsequent step adds 8 more

    return C


def solve(target: int = 1000) -> int:
    """Find the smallest n such that C(n) equals the target value.

    Uses iterative deepening with a geometric progression on the search bound.

    Args:
        target: The desired value of C(n). Default 1000.

    Returns:
        The smallest positive integer n for which C(n) == target.

    Raises:
        RuntimeError: If not found within a reasonable bound.
    """
    max_n = 5000
    while max_n < 10 ** 8:
        print(f"Searching up to n = {max_n}...")
        C = count_layers(max_n)

        for n in range(1, max_n + 1):
            if C[n] == target:
                return n

        max_n *= 2

    raise RuntimeError("Not found within search bound")


def verify_samples():
    """Verify C(n) values for the sample data given in the problem."""
    C = count_layers(200)
    samples = {22: 2, 46: 4, 78: 5, 118: 8, 154: 10}
    for n, expected in samples.items():
        actual = C[n]
        status = "PASS" if actual == expected else f"FAIL (got {actual})"
        print(f"  C({n}) = {actual}  {status}")


if __name__ == "__main__":
    t0 = perf_counter()

    print("Sample verification:")
    verify_samples()

    print()
    result = solve(1000)
    elapsed = perf_counter() - t0
    print(f"\nAnswer: {result}")
    print(f"Time: {elapsed:.3f}s")

常量与导入

count_layers 函数

solve 函数

verify_samples 函数