文章目录
- 题目描述
- 思路分析
- 评价
题目描述
国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天)里,每天收到两枚金币;之后三天(第四、五、六天)里,每天收到三枚金币;之后四天(第七、八、九、十天)里,每天收到四枚金币 … … … 这种工资发放模式会一直这样延续下去:当连续 n n n 天每天收到 n n n 枚金币后,骑士会在之后的连续 n + 1 n+1 n+1 天里,每天收到 n + 1 n+1 n+1 枚金币( n n n 为任意正整数)。
你需要编写一个程序,确定从第一天开始的给定天数内,骑士一共获得了多少金币。
时间限制:1 s
内存限制:128 MB
- 输入
一个正整数,表示天数。范围 1 1 1 到 10000 10000 10000。 - 输出
骑士获得的金币数。 - 样例输入
6
- 样例输出
14
思路分析
乍看之下,此题可以用双层循环来解决。这里没有什么理论分析,我们直接讲代码。
首先定义一个计数器变量 coin
,初始值为 1 1 1,用于表示每天需要发放给骑士的金币数量。另设一个累加器变量 total
,初始值为 0 0 0,用于表示骑士获得的金币总数。
然后用外层循环来记录已发放金币的总天数,用内层循环来发放金币,当然了,内层循环也需要控制已发放金币的总天数。具体来讲,每发放一次金币,就需要累加一天。在下面的代码中,变量 i
和 day
都是用来记录天数的,i
用来记录 coin
枚金币发放的天数,day
用来记录已发放金币的总天数。每当内层循环结束之后,coin
的值需要加一。
/** Name: coin1.cpp* Problem: 金币* Author: Teacher Gao.* Date&Time: 2024/03/07 23:26*/#include <iostream>using namespace std;int main()
{int n;cin >> n;int total = 0, coin = 1;for (int day = 1; day <= n; ) {for (int i = 1; i <= coin && day <= n; i++) {total += coin;day++;}coin++;}cout << total << endl;return 0;
}
仔细研究题目描述之后,不难发现国王每天给骑士发放的金币熟练呈现如下规律:
1 2 2 3 3 3 4 4 4 4 5 5 . . . \begin{matrix} 1 \\ 2 & 2 \\ 3 & 3 & 3 \\ 4 & 4 & 4 & 4 \\ 5 & 5 & ... \end{matrix} 12345234534...4
也就是说,金币总数为 t o t a l = 1 2 + 2 2 + 3 2 + . . . + k 2 + d total = 1^2 + 2^2 + 3^2 + ... + k^2 + d total=12+22+32+...+k2+d,其中 d d d 的值需要分情况讨论。这个式子表明,国王已经发放了 d a y = 1 + 2 + 3 + . . . + k = k × ( k + 1 ) 2 day = 1 + 2 + 3 + ... + k = \frac{k \times (k + 1)}{2} day=1+2+3+...+k=2k×(k+1) 天的金币。若 n = d a y n = day n=day,那么接下来就不需要发放金币了,即 d = 0 d = 0 d=0。否则,剩余的 n − d a y n - day n−day 天国王每天需要给骑士发放 k + 1 k + 1 k+1 枚金币,那么 d = ( n − d a y ) × ( k + 1 ) d = (n - day) \times (k + 1) d=(n−day)×(k+1),这里我们假设 n − d a y < k + 1 n - day < k + 1 n−day<k+1。
于是,我们可以用一个循环来枚举 k k k,并用 d a y day day 与 n n n 的大小来控制循环。具体来说,若 d a y ≤ n day \le n day≤n(事实上, d a y < n day < n day<n 也是正确的,请自行分析),那么就将 k 2 k^2 k2 累加到 t o t a l total total 中,否则就结束循环。并在循环结束后,将 ( n − d a y ) × ( k + 1 ) (n - day) \times (k + 1) (n−day)×(k+1) 累加到 t o t a l total total 中。
分析到此就先告一段落。接下来我们讲讲代码中的细节,我们用变量 n
不断地减去 k
,来代替每次将 k
累加到 day
中。于是循环条件可以写成 k <= n
(写成 k < n
也是正确的,可以自行分析),(n - day) * (k + 1)
可以写成 n * k
。之所以写成 n * k
而不是 n * (k + 1)
,是因为在代码中我们将 k
的初始值设为了 1 1 1,并且在累加完 total
之后才进行 k++
,也就是说每次循环结束后,k
的值都是下一次需要发放的金币数量。此外,这种写法也导致了 n -= k
和 k++
两行代码的顺序不能调换,若要调换,则需把 n -= k
改写为 n -= k - 1
。
/** Name: coin2.cpp* Problem: 金币* Author: Teacher Gao.* Date&Time: 2024/03/07 23:30*/#include <iostream>using namespace std;int main()
{int n;cin >> n;int total = 0, k = 1;while (k <= n) {total += k * k;n -= k;k++;}total += n * k;cout << total << endl;return 0;
}
基于上述分析,我们可以得到不等式 d a y = k × ( k + 1 ) 2 ≤ n day = \frac{k \times (k + 1)}{2} \le n day=2k×(k+1)≤n,即 k 2 + k − 2 n ≤ 0 k ^2 + k - 2 n \le 0 k2+k−2n≤0。用求根公式可以求得方程 x 2 + x − 2 n = 0 x^2 + x - 2 n = 0 x2+x−2n=0 的正根 x 1 = − 1 + 1 + 8 n 2 x_1 = \frac{-1 + \sqrt{1 + 8 n}}{2} x1=2−1+1+8n,于是 k = ⌊ x 1 ⌋ k = \lfloor x_1 \rfloor k=⌊x1⌋。
求出 k k k 之后,只需求出 t o t a l = 1 2 + 2 2 + 3 2 + . . . + k 2 + d total = 1^2 + 2^2 + 3^2 + ... + k^2 + d total=12+22+32+...+k2+d 即可,其中 d = ( n − d a y ) × ( k + 1 ) d = (n - day) \times (k + 1) d=(n−day)×(k+1)。至于 ∑ i = 1 k i 2 = 1 2 + 2 2 + 3 2 + . . . + k 2 \sum_{i = 1}^{k} i^2 = 1^2 + 2^2 + 3^2 + ... + k^2 ∑i=1ki2=12+22+32+...+k2,可以用公式 ∑ i = 1 k i 2 = n × ( n + 1 ) × ( 2 n + 1 ) 6 \sum_{i = 1}^{k} i^2 = \frac{n \times (n + 1) \times (2n + 1)}{6} ∑i=1ki2=6n×(n+1)×(2n+1) 直接求出,该求和公式可以参考博主的博文关于和式的探讨(4)—— 一般性的方法。不过需要注意下面程序中对此公式的改写,主要目的是为了防止计算过程的溢出。
/** Name: coin3.cpp* Problem: 金币* Author: Teacher Gao.* Date&Time: 2024/03/07 23:38*/#include <iostream>
#include <cmath>using namespace std;int main()
{int n;cin >> n;int k = (sqrt(1 + 8 * n) - 1) / 2;int total = k * (k + 1) / 2 * (2 * k + 1) / 3;total += (n - k * (k + 1) / 2) * (k + 1);cout << total << endl;return 0;
}
评价
此题的优秀之处在于,融合了诸如一元二次方程、向下取整等数学知识的同时,也确保了那些数学基础较弱的选手通过循环嵌套的方式进行求解。
不过循环嵌套的代码并没有那么容易写清楚。博主在教学过程中发现,绝大多数学生可以想到循环嵌套的方式,其中一部分学生会发现该程序只能计算诸如 n = 1 , 3 , 6 , 10 , . . . n = 1, 3, 6, 10,... n=1,3,6,10,... 之类的三角形数的答案,当 n n n 的值介于两个三角形数之间时,即 k − 1 < n ≤ k k - 1 < n \le k k−1<n≤k 时,计算结果总是 k k k 对应的结果。这是因为内层循环没有控制变量 day
的值,以至于 day
的值会超出 n n n 的范围。
可以想到第二种做法的学生,博主在教学过程中只发现了少数几个,而且这少数的几个学生都是奥数成绩不错的学生。至于第三种做法,目前尚且没有见到学生独立写出来过。
此题有着较好的区分度,以及开放性的解题方式,考察维度很立体。容易想到的循环嵌套方法,并不容易写清楚代码;容易写代码的数学方法,并不容易弄清楚代码中的细节。但是只要学生在逻辑思维和数学思维中有一方面较为突出,就可以解出此题。