P1150 Peter 的烟 —— 一道看似绕、其实一步推完的小题
一、题目复述
Peter 有 n 根烟,每抽完 k 根烟的烟蒂可以换一根新烟。Peter 可以无限次地用烟蒂换烟。问 Peter 一共能抽多少根烟。
数据范围:1 ≤ n, k ≤ 10⁶。
样例:n = 3, k = 2 → 输出 5(抽完 3 根得 3 个烟蒂 → 换 1 根 → 抽完又得 1 个烟蒂,共 5 根)。
二、直觉:循环模拟
最直观的写法是「不断用烟蒂换烟,直到不够为止」:
1 |
|
while 的语义很清楚:每轮尽量多换,换剩下的「零头」和新烟的烟蒂合并进入下一轮。复杂度 O(答案 / k),n 接近 10⁶ 时最多也就跑几十万次,没问题。
这种「模拟到不能换为止」的写法在任何「换东西」的题目里都适用,是最不容易出错的通用解。
三、推导:一步到位的公式
如果想把 O(答案) 压到 O(1),可以列个方程。设最终抽到 x 根烟,那么:
- 其中
n根是直接拿到的; - 另外
x − n根是用烟蒂换来的。
每抽一根烟就会多出一个烟蒂,所以抽完
x根烟手里就有x个烟蒂。
但最后一根烟的烟蒂是换不出新烟的(题目要求「换烟」发生在抽完之后,这一根之后 Peter 就停了)。所以实际「能用来换烟的烟蒂数」是 x − 1。
而这些烟蒂要凑出 x − n 根新烟,每根新烟需要 k 个烟蒂,所以:
1 | x - n = (x - 1) / k |
两边同乘 k:
1 | k * (x - n) = x - 1 |
展开:
1 | kx - kn = x - 1 |
把它化成「整除友好」的形式(避免浮点):
1 | x = n + (n - 1) / (k - 1) |
验证一下样例 n = 3, k = 2:x = 3 + (3 - 1) / (2 - 1) = 3 + 2 = 5 ✅。
四、O(1) 代码
1 |
|
只用三次加法、一次除法,没有循环。
五、为什么 O(1) 公式可以一次到位
while 循环和 O(1) 公式本质是同一个过程——只是把循环展开成了「直接算出总共换到了多少根烟」。所以这两个写法得到的答案完全一样:
| n | k | 公式 n + (n-1)/(k-1) |
模拟结果 |
|---|---|---|---|
| 3 | 2 | 3 + 2/1 = 5 |
5 |
| 10 | 3 | 10 + 9/2 = 14 |
14 |
| 100 | 5 | 100 + 99/4 = 124 |
124 |
实测 Python 一行就能对上:
1 | def peter(n, k): |
六、边界与极端情况
k = 1:每抽 1 根烟就能换 1 根,理论上 Peter 抽不完。题目保证k ≥ 2,所以不会出现k - 1 = 0的除零;但写代码时还是要心里有数——k = 1时这个公式没意义。n = 1:手里只有 1 根烟、1 个烟蒂,k ≥ 2换不成新烟,答案就是 1。代入公式1 + 0 / (k - 1) = 1✅。n = k:每抽k根烟能换 1 根,烟蒂刚好凑够。但n - 1个烟蒂也能凑出 1 个新烟,所以答案是n + 1。例如n = k = 3:抽 3 根得 3 个烟蒂 → 换 1 根 → 抽完又得 1 个烟蒂,共 4 根。代入公式3 + (3 - 1) / (3 - 1) = 3 + 1 = 4✅。long long防溢出:n, k ≤ 10⁶时答案最大约为10⁶ × 2 = 2 × 10⁶,int都够用。但 O(1) 公式里n * k等中间量如果以后扩展到n, k ≤ 10⁹就会爆,保险起见都开long long。
七、易错点清单
- 少 1 的细节:要记住「最后一根烟的烟蒂不能换」——这是「方程左右差 1」的根源。
k = 1的除零:题目保证k ≥ 2,但自己造数据测试时要绕开。- 公式 vs 循环:写循环不容易出错,写公式容易把
n - 1漏成n。两种写法都备着,互相验证。 butts % k别忘了:换完后剩下的「零头」烟蒂要保留进下一轮,否则会少算。n = 1, k = 2测一下:最最简单的边界,输出必须是 1。能验证while进入条件butts >= k正确、n + (n-1)/(k-1)不越界。
八、小结
这类「手里有 n 个东西,每 k 个换 1 个」问题的标准两步走:
- 模拟版本:循环
while (have >= k),每轮换到新物品,旧物品余数 + 新物品进下一轮。写起来慢但稳; - 公式版本:列「抽到 x 根」与「换出 x − n 根」的等式,注意「最后一个不能换」,化简后一次性算出。写起来快,但要小心
k = 1等边界。
两道写法互相验证,复杂数据 / 极端样例都可以放心过。
