151. 偏好A5纸(A Preference for A5)

一家印刷店每周处理 16 个批次(作业),每个批次需要一张 A5 尺寸的特殊校样纸。

每个星期一早晨,主管打开一个新信封,里面有一张 A1 尺寸的特殊纸张。

主管将其对半裁开,得到两张 A2 尺寸的纸张。然后将其中一张对半裁开,得到两张 A3 尺寸,以此类推,直到得到本周第一个批次所需的 A5 尺寸纸张。

所有未使用的纸张都放回信封中。

在每个后续批次开始时,主管从信封中随机取出一张纸。如果是 A5 尺寸,则直接使用。如果更大,则重复"对半裁开"过程直到得到一张 A5 尺寸的纸张,剩余纸张始终放回信封。

求一周内(不包括第一个和最后一个批次),主管发现信封中只有一张纸的次数的期望值。

答案保留六位小数,格式为 x.xxxxxx。

分析:用概率传播 DP 建模。状态为四种尺寸纸张的数量 \((a_2,a_3,a_4,a_5)\),初始状态为第 1 批次结束后的 \((1,1,1,1)\)。每批随机抽取一张并裁切,根据抽到的尺寸产生不同的转移。对第 2 到第 15 批次,累加信封中仅有一张纸的概率。

一、数学背景

A 系列纸张的面积满足:1 张 A1 = 2 张 A2 = 4 张 A3 = 8 张 A4 = 16 张 A5。以 A5 为单位,初始 A1 等价于 16 个 A5。每消耗一批,面积减少 1。状态 \((a_2,a_3,a_4,a_5)\) 满足面积守恒:

$$8a_2+4a_3+2a_4+a_5 = 16 - k$$

其中 \(k\) 为已完成的批次数。

第 1 批后(\(k=1\)),状态为 \((1,1,1,1)\),总面积 \(8+4+2+1=15\)

二、算法设计

\(n=a_2+a_3+a_4+a_5\) 为当前信封中纸张总数。

从状态 \((a_2,a_3,a_4,a_5)\) 出发,下一批次的四种可能转移:

使用字典存储状态→概率分布。初始分布为 {(1,1,1,1): 1.0}。对第 2 到第 15 批(共 14 批),累加 \(\sum(\text{counts})=1\) 的状态概率,再转移到下一分布。

三、复杂度分析

状态数受面积约束限制,最大值约数十个。14 步转移,总运算量极小。实测约 65 毫秒。

四、代码实现与说明

"""
Project Euler Problem 151: A Preference for A5

Compute the expected number of times (excluding first and last batch)
that the envelope contains exactly one sheet of paper at the start of a batch.
"""

from collections import defaultdict


def solve() -> float:
    """
    Simulate the probability distribution over states for batches 2 through 15.

    State: (a2, a3, a4, a5) = counts of A2, A3, A4, A5 sheets in the envelope.
    Initial state (after batch 1): (1, 1, 1, 1).
    """
    dist = {(1, 1, 1, 1): 1.0}
    expected_singles = 0.0

    for _ in range(14):
        for state, prob in dist.items():
            if sum(state) == 1:
                expected_singles += prob

        next_dist = defaultdict(float)
        for (a2, a3, a4, a5), prob in dist.items():
            total_sheets = a2 + a3 + a4 + a5
            if total_sheets == 0:
                continue

            if a5 > 0:
                p = prob * a5 / total_sheets
                next_dist[(a2, a3, a4, a5 - 1)] += p

            if a4 > 0:
                p = prob * a4 / total_sheets
                next_dist[(a2, a3, a4 - 1, a5 + 1)] += p

            if a3 > 0:
                p = prob * a3 / total_sheets
                next_dist[(a2, a3 - 1, a4 + 1, a5 + 1)] += p

            if a2 > 0:
                p = prob * a2 / total_sheets
                next_dist[(a2 - 1, a3 + 1, a4 + 1, a5 + 1)] += p

        dist = next_dist

    return expected_singles


if __name__ == "__main__":
    result = solve()
    print(f"{result:.6f}")

代码说明(按执行顺序):

导入

solve

主循环(14 次迭代)

累加单张概率

状态转移

输出