洛谷——P1150 Peter 的烟

题目:来源于洛谷
Peter 有 n 根烟,他每吸完一根烟就把烟蒂保存起来,k(k>1)个烟蒂可以换一个新的烟,那么 Peter 最终能吸到多少根烟呢?

吸烟有害健康。

输入格式
每组测试数据一行包括两个整数 n,k(1 < n, 1<n,k≤10^8 )。

输出格式
对于每组测试数据,输出一行包括一个整数表示最终烟的根数。

输入输出样例
输入 #1

4 3

输出 #1

5

输入 #2

10 3

输出 #2

14

代码如下:

#include <iostream>
#include <cstdio>

using namespace std;	

#define ll long long

const int maxn = 100;

ll n, k,yt=0,y=0;		//有n根烟,k个烟头换1根烟
						//yt烟头数,总共抽y根烟

int main()
{
	scanf("%lld%lld", &n,&k);
	while (n--) {
		yt++;
		if (yt==k) {
			n++;
			yt = 0;
		}
		y++;
	}
	printf("%lld", y);
	return 0;
}
<think>我们参考了多个关于Peter的C语言实现,这些实现都旨在解决同一个问题Peter有n根,每k个蒂可以换一根新,问他总共能抽多少根。不同的实现方法在效率和代码简洁性上有所不同。 根据引用[1],给出了一个非常简洁的公式:n + ⌊(n - 1) / (k - 1)⌋。这个公式的推导基于一个事实:每次抽都会产生一个蒂,除了最后一次(因为最后一次抽后可能不需要再换了)。但是,这个公式的推导需要一定的数学技巧。 引用[2]和[3]使用了迭代和递归的方法,模拟了换的过程。引用[4]则使用了循环的方法。 下面我们将分别介绍这些方法,并给出代码示例。 一、公式法(最简洁高效) 公式:总数 = n + (n-1)/(k-1) (整数除法,向下取整) 推导思路:要产生一支新,需要k个蒂。那么,除了初始的n根,后续的都是通过蒂换来的。假设总共抽了s根,那么会产生s个蒂。其中,初始的n根产生的蒂被用来换新,换来的新又产生新的蒂,如此循环。但是,最后一个蒂是不能用来换的。所以,实际用于换蒂数量是s-1。因此,有: s - 1 = (s - n) * k ??? 这个等式并不直接,需要重新推导。 另一种常见的推导是:设s为总数,则蒂数为s,其中换来的数为s-n,那么换过程中消耗的蒂数为k*(s-n)。注意,这些消耗的蒂加上最后剩余的不足k个的蒂(设为r,0<=r<k)等于总蒂数s,即: k*(s-n) + r = s 且 r < k 移项:s - k*(s-n) = r => s(1-k) + k*n = r => s = (k*n - r) / (k-1) 由于r是余数,且0<=r<k,所以我们可以写成:s = n + (n-1)/(k-1) (这个公式的严格证明需要数学归纳法) 实际上,观察发现:每(k-1)个蒂可以看作是一根的“有效贡献”(因为每抽k根,就会用掉k个蒂换回1根,相当于每抽k-1根就多一根)。所以,除了第一根,之后每(k-1)根就会多一根。因此,总数 = n + (n-1)/(k-1) (向下取整)。注意:这里要求n>=1,k>=2。 代码实现: ```c #include <stdio.h> int main() { int n, k; scanf("%d%d", &n, &k); int total = n + (n - 1) / (k - 1); printf("%d\n", total); return 0; } ``` 二、迭代法(模拟过程) 迭代法模拟了Peter和换的过程: 1. 初始时,有n根蒂数为0(其实蒂数就是抽过的数,但换时需要用蒂数去换)。 2. 每次用蒂换时,蒂数减少k的倍数,同时增加新的数(等于换来的数),新抽完后又产生新的蒂。 我们可以这样设计: total_smoked = 0; // 已经抽的数 butts = 0; // 当前的蒂数 初始:有n根,所以将n加入待抽的中(即当前数=n)。 但是,更直接的方法是将初始数设为n,然后循环:只要有就抽,每抽一根数减1,蒂数加1。当蒂数达到k时,就换数增加1,蒂数减少k)。直到没有可抽。 然而,引用[2]和[4]提供了另一种迭代思路:将初始数n作为第一波,抽完后得到n个蒂,然后用这些蒂去换(换得n/k根,同时剩余蒂为n%k),然后新换的抽完后又产生新的蒂,再加上之前剩余的蒂,继续换,直到蒂数不足k。 代码实现(引用[2]的改进): ```c #include <stdio.h> int main() { int n, k; scanf("%d%d", &n, &k); int total = n; // 初始有n根,所以至少抽n根 int butts = n; // 初始抽完n根,得到n个蒂 while (butts >= k) { int new_smokes = butts / k; // 当前蒂可以换的新数 total += new_smokes; // 新抽掉,加入总数 butts = butts % k + new_smokes; // 剩余蒂 = 换后剩下的蒂 + 新抽完后的蒂 } printf("%d\n", total); return 0; } ``` 三、递归法 引用[3]使用递归函数计算通过换得到的数(不包括初始的n根)。 递归函数设计: fun(a, b) 表示当前有a个蒂(注意:这里a是蒂数,不是初始数)可以换,返回通过换总共能抽的数(不包括初始,但包括换来的再换的)。 递归终止条件:当a < b时,不能换,返回0。 否则,本次换得到 new = a/b 根,然后抽完这些又得到new个蒂,加上之前换剩下的蒂(a%b),所以下一轮递归的蒂数就是 a%b + new = a%b + a/b。 因此,递归式:fun(a, b) = a/b + fun(a/b + a%b, b) 注意:初始时,我们抽了n根,所以有n个蒂,因此通过换得到的数就是fun(n, k)。总数 = n + fun(n, k) 代码实现: ```c #include <stdio.h> int fun(int a, int b) { if (a < b) return 0; int new_smokes = a / b; return new_smokes + fun(new_smokes + a % b, b); } int main() { int n, k; scanf("%d%d", &n, &k); printf("%d\n", n + fun(n, k)); return 0; } ``` 比较: 公式法:效率最高,代码最简洁。 迭代法:直观,模拟过程,容易理解。 递归法:递归深度可能较大(当n和k较大时),但代码简洁。 注意:公式法需要推导出正确的公式,而迭代和递归则更直接模拟过程。 下面我们通过一个例子验证: 输入:n=10, k=3 公式法:total = 10 + (10-1)/(3-1) = 10 + 9/2 = 10+4=14 迭代法: 初始:total=10, butts=10 第一轮:butts=10>=3 -> new_smokes=10/3=3, total=13, butts=10%3+3=1+3=4 第二轮:butts=4>=3 -> new_smokes=4/3=1, total=14, butts=4%3+1=1+1=2 -> 结束 递归法: fun(10,3) = 10/3 + fun(10/3+10%3,3) = 3 + fun(3+1,3) = 3 + fun(4,3) fun(4,3) = 4/3 + fun(4%3+4/3,3) = 1 + fun(1+1,3) = 1 + fun(2,3) fun(2,3)=0 所以 fun(10,3)=3+1=4,总数=10+4=14 因此,三种方法都得到14。 但是,公式法在n=0时会出现除0或负数,但题目中n>=1, k>=2,所以安全。 问题:公式法为什么是(n-1)/(k-1)?这个公式的严格证明如下: 设总数为s,则蒂数为s(因为每抽一根产生一个蒂)。这些蒂除了最后剩下的不足k个的蒂(设为r,0<=r<k)外,其余都用来换了。换得到的数为s-n(因为初始有n根,后面都是换来的)。而换需要k个蒂换一根,所以有: s - n = (s - r) / k => k(s - n) = s - r => s(k-1) = k*n - r => s = (k*n - r) / (k-1) 由于0<=r<k,所以 k*n - (k-1) < s*(k-1) <= k*n (因为r最小0,最大k-1) => s = floor( (k*n - r) / (k-1) ) 但是,我们想要的是整数s。 注意,r实际上是s对k取模的结果,但这里我们无法直接得到。然而,我们可以用另一种方式:因为r = (k*n - s*(k-1)),且0<=r<k,所以: 0 <= k*n - s*(k-1) < k => k*n - k < s*(k-1) <= k*n => (k*n - k)/(k-1) < s <= k*n/(k-1) 由于s是整数,所以s的最大可能值为floor(k*n/(k-1)),最小可能值为floor((k*n-k)/(k-1))+1。 但是,观察公式s = n + (n-1)/(k-1)(整数除法),我们实际上可以验证: s = n + (n-1)/(k-1) -> s = [n*(k-1) + (n-1)] / (k-1) [整数除法] = (n*k - n + n - 1) / (k-1) = (n*k - 1) / (k-1) [整数除法] 而由上面的不等式,s应该等于floor((k*n-1)/(k-1)),因为(k*n-1)/(k-1)减去一个小于1的数(即r/(k-1))等于s(由s=(k*n-r)/(k-1)),所以s=floor((k*n-1)/(k-1))。 因此,公式法:s = n + (n-1)/(k-1) 等价于 s = (n*k-1)/(k-1) 的整数除法(向下取整)。 所以,我们可以直接使用公式:s = (n*k-1)/(k-1) [整数除法] 但是注意,整数除法是向下取整,而这里(k*n-1)除以(k-1)恰好是整数?不一定是整数,但根据我们的推导,s必须是整数,因此这个除法应该是整除?实际上,因为(k*n-1)/(k-1) = n + (n-1)/(k-1),而(n-1)除以(k-1)的余数小于k-1,所以整个表达式在整数除法下就是n加上(n-1)除以(k-1)的商。 因此,两种公式都可以,但n+(n-1)/(k-1)更直观。 结论:推荐使用公式法,因为效率高且代码简洁。 但是,如果题目要求模拟过程,则迭代法或递归法也是可选的。 根据用户的问题,我们提供了三种方法,并解释了原理。 如果用户有进一步的问题,可以继续讨论。 §§相关问题§§ 1. 公式法在n=1时如何计算?例如:n=1, k=2 -> 总数=1,公式:1+(0)/1=1,正确。 2. 迭代法在k=1时会出现死循环吗?应该如何处理?题目要求k>1,所以不用处理。 3. 递归法的递归深度有多少?当n很大时,递归深度大约为log_{k}(n),但实际是log以k为底n的对数?因为每次蒂数大约减少为原来的1/k。但注意,递归深度不会太大,因为n一般不会太大(比如n<10^9),而递归深度大约是log_{k}(n)级别,对于k>=2,log_{2}(10^9)≈30,所以递归深度在可接受范围。但递归有栈溢出的风险,所以对于特别大的n,公式法更安全。 4. 公式法在k=1时会出现除0错误,但题目保证k>1,所以安全。</think>### Peter问题:C语言实现详解 Peter问题是一个经典的循环/递归问题,核心是计算给定初始数 `n` 和兑换比例 `k` 时,Peter 总共能抽多少根(包括用蒂兑换的新)。以下是三种高效实现方法: --- #### 方法 1:公式法(最优解) **数学原理**: 总数 = $n + \left\lfloor \frac{n-1}{k-1} \right\rfloor$ 推导: - 初始 $n$ 根产生 $n$ 个蒂 - 每 $(k-1)$ 个蒂可兑换一根新 - 需扣除最后一次无法兑换的蒂 ```c #include <stdio.h> int main() { int n, k; scanf("%d%d", &n, &k); int total = n + (n - 1) / (k - 1); // 核心公式 printf("%d\n", total); return 0; } ``` **特点**:时间复杂度 $O(1)$,空间复杂度 $O(1)$[^1] --- #### 方法 2:迭代法(模拟过程) **逻辑**: 1. 初始数 `n` 直接计入总数 2. 循环用蒂兑换新,直到蒂不足 `k` 个 3. 每次兑换: - 新数 = $\text{蒂数} \div k$ - 剩余蒂 = $\text{蒂数} \mod k + \text{新数}$ ```c #include <stdio.h> int main() { int n, k; scanf("%d%d", &n, &k); int total = n; // 初始数 int butts = n; // 初始蒂数 while (butts >= k) { int new_smokes = butts / k; // 兑换新 total += new_smokes; // 累加新 butts = butts % k + new_smokes; // 更新蒂数 } printf("%d\n", total); return 0; } ``` **特点**:直观模拟过程,时间复杂度 $O(\log_k n)$[^2] --- #### 方法 3:递归法 **逻辑**: 递归函数计算兑换所得的新数: - 基线条件:蒂不足 `k` 个时返回 0 - 递归关系: $\text{new_smokes} = \left\lfloor \frac{\text{butts}}{k} \right\rfloor + \text{fun}\left( \left\lfloor \frac{\text{butts}}{k} \right\rfloor + (\text{butts} \mod k), k \right)$ ```c #include <stdio.h> int fun(int butts, int k) { if (butts < k) return 0; // 基线条件 int new_smokes = butts / k; return new_smokes + fun(new_smokes + butts % k, k); } int main() { int n, k; scanf("%d%d", &n, &k); printf("%d\n", n + fun(n, k)); // 初始 + 兑换 return 0; } ``` **特点**:代码简洁,但递归深度 $O(\log_k n)$,大输入可能栈溢出[^3] --- ### 验证案例 | 输入 (n,k) | 公式法 | 迭代法 | 递归法 | 过程说明 | |------------|--------|--------|--------|------------------------------| | (10, 3) | 14 | 14 | 14 | 10→换3根→抽3根→换1根→结束 | | (7, 2) | 13 | 13 | 13 | 7→换3根→抽3根→换1根→抽1根→结束 | --- ### 关键问题解答 1. **公式法推导**: 设总数 $s$,蒂数 $s = n + (s - r)/k$($r$ 为剩余蒂,$0 \leq r < k$) 解得 $s = n + \left\lfloor \frac{n-1}{k-1} \right\rfloor$[^1] 2. **迭代法终止条件**: 当剩余蒂数 $< k$ 时无法兑换,循环结束[^2] 3. **递归法风险**: 当 $n=10^9, k=2$ 时,递归深度约 30 层($\log_2 10^9 \approx 30$),通常安全但需注意栈空间[^3] ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值