小欧喝水
小欧喝水
OPPO 笔试真题 · 2024 秋招 · 后端岗
判题请到牛客原题提交。
题目描述
小欧拿了个杯子排成了一排,其中有个杯子装满了水,剩余的个杯子为空的。小欧每回合的操作如下:
随机选择一个杯子。
杯子是空的。回合直接结束。
杯子是满的。如果小欧上一回合喝过了水,则回合结束;否则将喝完这杯水,回合结束。
小欧想知道,她喝完所有水的回合数期望是多少?
输入格式
两个正整数,用空格隔开。
输出格式
一个浮点数,代表期望的回合数。如果你的答案和正确答案的误差不超过,则认为答案正确。
示例
示例 1
输入:
1 1输出:
1.000000000说明:
只有一杯水,第一回合就可以喝完。
示例 2
输入:
2 1输出:
2.000000000说明:
有50%的概率1回合喝完,有25%的概率需要2回合 ,有12.5%的概率需要3回合……
总期望为0.52+0.253+0.125*4+……=2
示例 3
输入:
2 2输出:
4.000000000说明:
第一回合有100%的概率喝一杯水。
第二回合无论是否选到有水的杯子都不会喝水。
0.53+0.254+0.125*5+...=4
解题思路
第一步:把过程抽象成状态
每回合无论选到什么,回合都会结束;真正的区别只有「这回合有没有喝到水」。
喝到水的唯一条件是:选中的杯子是满的,且上一回合没喝过水。
杯子之间除了「满 / 空」没有区别,所以不需要记录具体是哪几个杯子满了,
只需要记录还剩几杯水。再记住「上一回合有没有喝过」这个标记,
状态就完整了。
定义 :还剩 杯水、且上一回合没喝过水时,喝完剩下所有水
还需要的期望回合数。
第二步:先看上一回合喝过水的情况
如果上一回合喝过水,那么这一回合一定喝不到(哪怕选中的是满杯):
选到空杯,回合结束;选到满杯,因为要「冷却」,也不喝,回合结束。
两种情况都是白白消耗 1 回合,且 不变,之后标记回到「没喝过」。
所以「冷却」的代价固定是 1 回合。
第三步:写出期望方程
只剩 1 杯水时,每回合有 的概率选中它,选中就喝完、立刻结束,
所以
(几何分布的期望,最后一次喝水之后不需要再冷却。)
当 时,这一回合:
- 以 的概率选中满杯 → 喝掉一杯,接着花 1 个冷却回合,再面对 杯;
- 以 的概率选中空杯 → 什么都没发生,仍是 杯、标记为「没喝过」。
把含 的项移到左边,两边除以 ,化简得
第四步:累加得到闭式
从 开始一路加到 :
也就是 ,其中 是第 个调和数。
递推一遍即可,不需要解方程也不需要模拟。
完整代码实现
import sys
from math import fsum
def solve() -> None:
"""按 E(k) = n * H(k) + k - 1 直接算,调和级数用 fsum 保证精度"""
data = sys.stdin.buffer.read().split()
if not data:
return
n = int(data[0])
k = int(data[1])
harmonic = fsum(1.0 / i for i in range(1, k + 1))
ans = n * harmonic + (k - 1)
sys.stdout.write(f"{ans:.9f}\n")
if __name__ == "__main__":
solve()示例推演
示例 1:
只有一杯水,第一回合必然选中它并喝掉,共 1 回合。
公式:。✓
示例 2:
每回合有 的概率选中那杯水。第 回合才喝到的概率是
,所以期望回合数
公式:。✓
示例 3:
| 回合 | 状态 | 发生了什么 |
|---|---|---|
| 1 | 2 杯满 | 必选中满杯,且上一回合没喝过 → 喝掉 1 杯,剩 1 杯 |
| 2 | 1 杯满 | 上一回合喝过水 → 无论选中什么都不喝,冷却 1 回合 |
| 3 起 | 1 杯满 | 每回合有 概率选中它,期望还要 回合 |
合计 回合。
换个角度验证:从第 3 回合开始数,第 回合才喝到的概率是
,于是总回合数 ,期望
对应题面给出的分布:3 回合(概率 )、4 回合()、
5 回合()……
公式:。✓
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 按状态解方程递推 | 保存 ,思路直观 | ||
| 闭式累加调和级数 | 推荐,只需一个累加器 |
, 的循环只需几十毫秒,没有必要做 的近似。
易错点总结
1. 漏掉「冷却回合」
最典型的错误是认为喝完一杯后下一回合还能接着喝。 时那样会算出 3,
而正确答案是 4 —— 第二回合是必然的冷却,第三回合开始才重新有机会。
2. 把 也加上冷却
最后一杯喝完,任务就结束了,不需要再空转一回合。所以
,而不是 。这一处差 1,会把 算成 2。
3. 浮点累加的误差被 n 放大
答案形如 , 可以到 。如果用普通 sum 累加
项,误差会被放大到 量级,超过 的要求。
用 math.fsum(精确求和)或 Kahan 补偿求和可以避免。
4. 把「选到满杯」误当成「一定喝到水」
选到满杯只是必要条件。上一回合喝过水时,选到满杯也不会喝,
杯子仍然是满的 —— 这一点决定了 在冷却回合不会减少。
5. 输出精度
题目要求误差不超过 ,输出 9 位小数足够,也便于和样例逐字比对。
扩展思考
- 如果取消冷却(每回合选中满杯就喝),期望退化成「集齐 个指定杯子」
的经典问题:。本题多的正是 个
冷却回合。 - 如果冷却只针对「连续两次选中同一个杯子」,状态里就必须记录上一次选的是
哪个杯子,状态数会从 涨到 。 - 调和数的近似 可以用来
在 极大时估算答案,但本题 ,直接循环更稳。