第四章 循环结构的程序设计¶
基础数学知识¶
- 分数与小数除法:分数表示除法;分子、分母都是整数时,数学上的分数仍可能不是整数。对应 1059—1061、1072、1078、1079、1082。
- 百分数与逐年增长:\(0.1\%=0.001\),每年增长 \(0.1\%\),下一年应在上一年的基础上乘 \(1.001\);利息也要分清按哪一部分本金计算。对应 1070、1074。
- 乘方与阶乘:正整数次幂 \(a^n\) 是 \(n\) 个 \(a\) 相乘;非零数的零次幂为 \(1\)。\(n!=1\times2\times\cdots\times n\),约定 \(0!=1\)。对应 2019、1069、1083、1084、2026、2033、1091—1093。
- 数列与递推关系:根据前面的项求后一项,例如 \(F_n=F_{n-1}+F_{n-2}\);更新时要保留计算所需的旧值。对应 1071、1078。
- 等差数列求和:\(1+2+\cdots+n=\frac{n(n+1)}2\),可通过正序与倒序配对推导。对应 2016、1081、2020、1100。
- 平面坐标、勾股定理与平方根:点 \((x,y)\) 到原点的距离是 \(\sqrt{x^2+y^2}\)。平方根表示“哪个非负数的平方等于这个数”。对应 1073、2030、2031。
- 向上取整:不小于原数的最小整数,例如 \(\lceil3.2\rceil=4\)、\(\lceil3\rceil=3\)。对应 1073,代码用
ceil。 - 余数与周期:只关心周期位置或末几位时,可保留除以周期或 \(10\) 的幂所得的余数;乘法每一步都取余,不改变最终余数。对应 1080、1082—1084、2033。
- 负数与整数除法:数学上的向下取整与 C++ 的向零截断不同。C++ 中
-38 / 10为 \(-3\),-38 % 10为 \(-8\),仍满足“被除数等于商乘除数加余数”。对应 1089。 - 质数、最大公约数与质因数分解:质数是大于 \(1\)、只有 \(1\) 和自身两个正约数的整数;最大公约数是两个正整数最大的公共约数;质因数分解是把整数写成质数的乘积。对应 2021、2030、2032、1098、1099。
- 完全平方数、立方与数位表示:完全平方数可以写成整数的平方;三位数 \(100a+10b+c\) 的各数位立方和是 \(a^3+b^3+c^3\)。对应 2029、2031。
- 用字母列方程与非负整数解:把数量、价格关系写成等式,非负整数包括 \(0\)。已知部分未知数后,可以用等式求出其余未知数,再检查是否满足条件。对应 2028、1101。
第一节 for语句¶
2016:【例4.1】for循环求和¶
提示 1:把手算加法交给循环
先写出1加到4的四次加法,下一次每次增加的是哪个数?
常见错误
不要把 sum 定义在循环内部,否则每次都会清零。用 \(n=3\) 检查总和是否依次变成 \(1,3,6\)。
自测
输入:1 → 预期输出:1
输入:100 → 预期输出:5050
自己试试:先选一个较小的 \(n\),自己把加法写出来。
再测 \(n+1\),两次答案应该相差多少?
参考代码
2017:【例4.2】输出偶数¶
提示 1:不满足条件就跳过
从1到6逐个看,哪些数需要输出?
输入1时,一个符合条件的数也没有,不要补0。
常见错误
不要从 \(0\) 开始输出,题目从 \(1\) 开始。\(n=1\) 时没有偶数,不输出字符,也不要补一个 \(0\)。
自测
输入:1 → 预期输出:不输出任何字符
输入:6 → 预期输出:2 4 6
自己试试:自己选一对相邻的奇数、偶数作为上限。
两次输出会多出哪个数?
方法一:逐个判断
方法二:每次增加 2
2018:【例4.3】输出奇偶数之和¶
提示 1:两份和分开记
从1到5,把奇数圈起来、偶数画下划线,各自求和。
提示 2:每个数只进一个总和
先设odd = 0、even = 0,i从1走到n:
按题目要求先输出偶数和;两者相加应等于哪题的结果?
常见错误
题目描述先说奇数,输出顺序却先要偶数。拿 \(n=3\) 检查,应输出 2 4。
自测
输入:1 → 预期输出:0 1
输入:10 → 预期输出:30 25
自己试试:自己选一个上限,分别手算奇数和、偶数和。
两者相加,能否用前面“for循环求和”那题的结果检查?
参考代码
2019:【例4.4】求阶乘¶
提示 1:阶乘不是把数加起来
把\(4!\)写成乘法,最开始的乘积应该用0还是1?
提示 2:每次多乘当前项
读入n,先设long long ans = 1;:
大类型要用在乘积变量上;算20的阶乘时int还能装下吗?
知识补充
\(5!=120\),\(6!=6\times5!=720\)。阶乘的感叹号是数学记号;在 C++ 里,! 表示逻辑非,不能用 n! 写阶乘。
常见错误
把乘积初始化为 \(0\),后面就一直是 \(0\)。C++ 的 ^ 不是乘方,也不能用它表示阶乘。
自测
输入:1 → 预期输出:1
输入:20 → 预期输出:2432902008176640000
自己试试:自己选一个能手算的 \(n\),再测 \(n+1\)。
后一结果应该是前一结果的多少倍?
参考代码
1059:求平均年龄¶
提示 1:总人数已给,不必保存所有年龄
先用15、16手算均值,为什么结果要带小数?
常见错误
sum / n 两边都是整数时会先丢掉小数,事后存进 double 也补不回来。用平均数不是整数的数据检查。
自测
输入:2 15 16 → 预期输出:15.50
输入:1 25 → 预期输出:25.00
自己试试:在题目年龄范围内选两个不同年龄,让平均数带小数。
这组数据能检查出整数除法的问题吗?
参考代码
1060:均值¶
提示 1:小数从读入时就保留
输入-1.5、0、3,若每个数先存int,会在哪里丢掉信息?
常见错误
不要用 int 读入小数;也不要照搬上一题的两位小数。用正负数抵消的情况检查。
自测
输入:2 1.0 3.0 → 预期输出:2.0000
输入:3 -1.5 0 3 → 预期输出:0.5000
参考代码
1061:求整数的和与均值¶
提示 1:一次读入得到两种结果
先手算1、2的总和与平均数,两项需要保留的小数位相同吗?
提示 2:先完整累加再算均值
sum = 0,每读入整数x就执行sum += x;,共读n次。
为什么第二项不能直接写sum / n?
常见错误
总和与均值之间要有空格。不要先做整数除法;用 \(1,2\) 检查小数部分。
自测
输入:2 1 2 → 预期输出:3 1.50000
输入:3 -2 0 2 → 预期输出:0 0.00000
自己试试:在题目范围内选几个正负数,让总和为零。
再只改其中一个数,先算出总和与均值各会怎样变化。
参考代码
1062:最高的分数¶
提示 1:第一个分数先当擂主
读完n,第一个分数先放到ans里,当擂主。
想一下,还有几个分数没读?
常见错误
不要每读到一个分数就直接覆盖答案。检查最高分在开头、末尾,以及只有一个分数的情况。
自测
输入:1 0 → 预期输出:0
输入:4 100 60 90 70 → 预期输出:100
自己试试:自己造一组分数,让最高分出现在最前面。
只调换位置,把它移到最后,答案应不应该变?
参考代码
1063:最大跨度值¶
提示 1:跨度只看最大和最小
对8、2、10、5,先找两端,再算跨度。
提示 2:两份纪录一起更新
第一个数x已读入,让maxn = minn = x,以后每读到一个数:
最后输出谁减谁?只有一个数时跨度是多少?
常见错误
不要用最后一个数减第一个数,输入不保证排好序。全部相同时,跨度应为 \(0\)。
自测
输入:1 7 → 预期输出:0
输入:4 8 2 10 5 → 预期输出:8
自己试试:把同一个数重复几次,先预测跨度。
再只改一个数,看看最大值和最小值分别怎样变化。
参考代码
1064:奥运奖牌计数¶
提示 1:三列分别累加
先把两天的金银铜奖牌上下对齐,每列各加一次。
提示 2:别把日总数和类别总数混在一起
三类总和suma、sumb、sumc从0开始,读一天的a b c后:
循环结束再算三类总数,检查输出是否恰好四项。
常见错误
不要把三类奖牌混在一个总和里后,就无法分别输出了。拿一天全为 \(0\) 的情况检查初始化。
自测
输入:1 0 0 0 → 预期输出:0 0 0 0
输入:2 1 2 3 2 0 1 → 预期输出:3 2 4 9
自己试试:自己造两天的数据,让某一类奖牌全为零。
四个输出之间,有什么关系可以用来检查?
参考代码
1065:奇数求和¶
提示 1:先在区间里圈奇数
对2到7手算,包含右端点7吗?
常见错误
不要漏掉右端点。i & 2 不能判断奇数;当前直接用 % 2。用端点本身为奇数的情况检查。
自测
输入:3 3 → 预期输出:3
输入:2 7 → 预期输出:15
自己试试:自己选一个包含奇数的合法区间。
只把右端点增加 \(1\),总和可能不变,也可能增加,你能分别造出来吗?
参考代码
1066:满足条件的数累加¶
提示 1:先列出17的倍数
在50到85中找出能被17整除的数,先算总和。
提示 2:符合才累加
区间端点m、n已读入,sum从0开始:
一个也找不到时,为什么直接输出sum即可?
常见错误
不要把条件记成不能整除,也不要混入其他除数。拿样例区间 \([50,85]\) 手算,符合条件的是 \(51,68,85\),和为 \(204\)。
自测
输入:50 85 → 预期输出:204
输入:1 2 → 预期输出:0
输入:16 18 → 预期输出:17
参考代码
1067:整数的个数¶
提示 1:次数不是数值之和
读到1、5、5、10,三个计数各是多少?
提示 2:每类有自己的计数器
让a、b、c从0开始,读到x时:
补读入循环,最后分三行输出,其他整数是否需要改变计数?
常见错误
cnt += x 是求和,统计个数应写 cnt++。没有出现的数字也要输出 \(0\)。
自测
输入:5 1 5 8 10 5,预期输出:
输入:2 8 8,预期输出:
自己试试:在题目允许的数字里,造一组没有 \(1\)、\(5\)、\(10\) 的数据。
再只替换其中一个数,预测哪一行输出会改变。
参考代码
1068:与指定数字相同的数的个数¶
提示 1:目标在这一题提前给出
先读n m,随后每个数都可以立刻与目标m比较。
常见错误
比较是 ==,不是赋值 =。先确认输入顺序,不要把 \(m\) 当成数列的第一个数。
自测
输入:5 2 2 1 2 3 2 → 预期输出:3
输入:1 9 8 → 预期输出:0
参考代码
1069:乘方计算¶
提示 1:先写出三次乘法
读入a、n,\(a^3\)是三个a相乘,不是a ^ 3。
常见错误
C++ 的 a ^ n 是按位异或,不是数学乘方。乘积初值不能是 \(0\);负数底数要区分奇数次幂和偶数次幂。
自测
输入:-2 3 → 预期输出:-8
输入:10 6 → 预期输出:1000000
输入:0 1 → 预期输出:0
输入:-1 10000 → 预期输出:1
参考代码
1070:人口增长¶
提示 1:第二年要用新人口
初始100人,第一年乘1.001;第二年还乘初始100,还是第一年的结果?
常见错误
不要把 \(0.1\%\) 写成 \(0.1\),也不要只算最初人口的固定增量。中途别四舍五入,最后输出时统一保留位数。
自测
输入:1 1 → 预期输出:1.0010
输入:100 2 → 预期输出:100.2001
参考代码
1071:菲波那契数¶
提示 1:只需记前两项
把1、1、2、3、5写出来,下一项由谁相加?
提示 2:先算新项,再更新旧项
初始化a = b = c = 1,从第3项开始:
把a = b挪到最前面试一下,旧的a还在吗?
再想想,只求第1项、第2项时,要不要进循环。
常见错误
如果先改 a 再算 c,就丢掉旧值了。用 \(n=1,2,3\) 分别检查初始化与循环起点。
自测
输入:1 → 预期输出:1
输入:2 → 预期输出:1
输入:30 → 预期输出:832040
输入:46 → 预期输出:1836311903
自己试试:自己选相邻的三个合法项号,分别运行。
不用查答案表,能用数列关系检查这三个输出吗?
参考代码
1072:鸡尾酒疗法¶
提示 1:先留住第一组数据
第一组是原疗法,后面每组都只和它比较。
用两个变量留住原人数、原有效人数,读下一组时不要覆盖它们。
提示 2:差一点和刚好相差,要分开
原疗法100人中50人有效,新疗法分别试55人、56人有效。
前一组恰好高出0.05,还算same;后一组才算better。
提示 3:把比较写成整数关系
下面这一步,用整数来比。
如果乘20这里没跟上,先展开后面的知识补充,看一下怎么换过来的。
原人数、有效人数是total、good,新组是newtotal、newgood,都用int。
循环外先保存int base = 20 * good;,每读一组再算:
先补diff > limit输出better,再补更差和其余情况。
知识补充:为什么乘20
原有效率为\(\frac{50}{100}\),新有效率为\(\frac{55}{100}\),两者相差\(\frac5{100}=\frac1{20}=0.05\)。
人数不同也一样,先用“有效人数除以总人数”求各自的有效率。
为了用整数准确比较,把“新减旧大于\(\frac1{20}\)”两边都乘20,再乘两个正的总人数,得到:
代码用diff、limit分别保存两边;大于才算更好,等于仍算same。
20 * good对每组都一样,用base在循环外算一次。
更差时比较diff < -limit,其余都是same。
人数不超过\(10000\),有效人数不超过总人数,中间乘积和差的绝对值最多为\(2\times10^9\),int可以保存。
常见错误
不要把原疗法算进输出,也不要用 >=。比较基准始终是第一组,不能每次换成上一组。
自测
输入:4 100 50 100 55 100 56 100 44,预期输出:
输入:2 100 50 100 45,预期输出:
自己试试:自己选一个原有效率,再配一个恰好高出 \(0.05\) 的新疗法。
先确认病例数都符合题目范围,再预测它与“多高出一点”的情况有何区别。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, total, good;
cin >> n >> total >> good;
int base = 20 * good;
for (int i = 1; i < n; i++) {
int newtotal, newgood;
cin >> newtotal >> newgood;
int diff = 20 * newgood * total - base * newtotal;
int limit = total * newtotal;
if (diff > limit) cout << "better" << '\n';
else if (diff < -limit) cout << "worse" << '\n';
else cout << "same" << '\n';
}
return 0;
}
1073:救援¶
提示 1:往返路程和接人时间分开算
坐标30、40离原点50米,船要去几趟单程?
每人上、下船总共1.5分钟,别只计一边。
提示 2:一次救援加进总用时
ret初值为0,当前坐标x、y用double,人数z用int:
处理完所有屋顶后才用ceil(ret)向上取整,能不能每趟先取整再加?
常见错误
不要把坐标读成整数,不要只算去程,也不要每次救援都先取整。可以画出往返路线,逐段核对。
自测
输入:1 30 40 1 → 预期输出:4
输入:2 0 0 1 0 0 1 → 预期输出:3
参考代码
1074:津津的储蓄计划¶
提示 1:先记一个月的钱去了哪里
每月先拿到300元,再付预算,最后把剩下的整百元存起来。
先拿第一个月试一下,预算180元,存多少,手里还剩多少?
提示 2:不够付时,先停下来
cash记手中现金,saved记已存的钱,都从0开始。
每月先给cash加300;若不够付cost,输出负月份并结束。
付完预算后再存钱:
提示 3:十二个月都够,才算年末的钱
妈妈保管的saved增加20%利息,手里的cash原样加回来。
不能把两笔钱先合起来再乘1.2。
常见错误
失败后不要再输出年末余额;利息只给妈妈保管的存款,不能把手头零钱也乘 \(1.2\)。如果模拟对不上,带着月份表、出错输入和实际输出一起找老师看。
自测
输入:300 300 300 300 300 300 300 300 300 300 300 300 → 预期输出:0
输入:0 0 0 0 0 0 0 0 0 0 0 0 → 预期输出:4320
输入:301 0 0 0 0 0 0 0 0 0 0 0 → 预期输出:-1
自己试试:试着安排预算,让前一个月还能支付,下一个月第一次不够。
先在纸上记下每月现金,再检查输出的是哪个月份。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int cash = 0, saved = 0;
for (int month = 1; month <= 12; month++) {
int cost;
cin >> cost;
cash += 300;
if (cash < cost) {
cout << -month;
return 0;
}
cash -= cost;
saved += cash / 100 * 100;
cash %= 100;
}
cout << cash + saved + saved / 5;
return 0;
}
1075:药房管理¶
提示 1:不够给这人,后面还要继续
库存5,先来的人要6、后一个要5;两人各能否取到?
常见错误
被拒绝时不要扣库存,也不能直接结束循环。后面需求较小的人仍可能拿到药。
自测
输入:5 3 6 5 1 → 预期输出:2
输入:10 2 4 6 → 预期输出:0
自己试试:自己造一组需求,让一个人被拒绝后,下一个人仍能成功取药。
这组数据主要检查哪一步有没有误改库存?
参考代码
1076:正常血压¶
提示 1:看的是连续,不是累计正常次数
把正常、正常、异常、正常画成四格,最长连续长度是多少?
提示 2:正常延长,异常中断
cnt记当前长度,ret记最长,两者从0开始。
两个血压值a、b要同时落在90到140、60到90,包含端点。
放进正常分支;异常只把cnt清0,能把ret也清0吗?
常见错误
两个范围都包含端点,且用 && 连接。不要遇到不正常就把历史最大值一起清零。
自测
输入:4 90 60 140 90 89 80 100 80 → 预期输出:2
输入:1 141 90 → 预期输出:0
自己试试:自己安排两段长度不同的正常记录,中间插入一次不正常。
先标出最长的一段,再想想怎样检查最后一段也被统计了。
参考代码
1077:统计满足条件的4位数¶
提示 1:先拆一个四位数
把1349的千、百、十、个位列出,用个位减去前三位之和。
等于0符合“严格大于0”吗?
常见错误
这里拆的是刚读到的 x,不是循环变量 i。差恰好为 \(0\) 时不计入,拿 \(1113\) 检查。
自测
输入:5 1234 1349 6119 2123 5017 → 预期输出:3
输入:2 1113 1001 → 预期输出:0
自己试试:自己造一个“个位恰好等于其余三位之和”的四位数。
再只改个位,让差变为正数,先预测计数会怎样变化。
参考代码
1078:求分数序列和¶
提示 1:先找下一项从哪里来
先写\(\frac21,\frac32,\frac53\),分子记为q,分母记为p。
下一项的分母用旧分子,下一项的分子用旧分子加旧分母。
提示 2:旧分母先留一份
q = 2、p = 1,double sum = 0;循环里先把当前分数加进总和:
第一轮算完,p = 2、q = 3。
第二轮你接着算,别光看这四行代码。
把第二次的旧值、新值写在纸上,再补重复n次的循环。
常见错误
不能先执行 p = q 再直接 q += p,那样会加到两个旧分子。手算两次更新,检查是否得到 \(3/2\)、\(5/3\)。
自测
输入:1 → 预期输出:2.0000
输入:3 → 预期输出:5.1667
输入:30 → 预期输出:48.8406
自己试试:自己续写前几项分数,再选一个小的项数手算总和。
如果结果不对,先检查是分数更新错了,还是求和错了。
参考代码
1079:计算分数加减表达式的值¶
提示 1:加减号跟项号走
前三项是1减二分之一加三分之一,奇数项和偶数项分别做什么?
提示 2:小数除法别丢掉1.0
sum用double从0开始,项号i从1到n:
用前两项检查符号,最后输出四位小数。
常见错误
1 / i 在 i > 1 时是整数 \(0\),应写 1.0 / i。用 \(n=2\) 检查是否得到 \(0.5000\)。
自测
输入:1 → 预期输出:1.0000
输入:2 → 预期输出:0.5000
参考代码
1080:余数相同问题¶
提示 1:从最小允许除数开始试
1总能让余数相同,但题目从大于1的数中找最小值。
所以从2开始逐个试。
提示 2:三个余数需要两次相等比较
a、b、c已读入,试除数x:
仿照补从2递增的循环,第一次找到后为什么可以停?
常见错误
C++ 不能写 a % x == b % x == c % x 表示三者相等,要拆成两个比较,用 && 连接。
自测
输入:300 262 205 → 预期输出:19
输入:5 5 5 → 预期输出:2
自己试试:先选一个希望得到的最小除数,再造三个余数相同的数。
它们会不会在更小的除数处就满足条件?
参考代码
1081:分苹果¶
提示 1:最少又互不相同
每人至少一个,把数量从小到大排,前四人最少可分1、2、3、4。
第n个人最少拿几个?
常见错误
从 \(0\) 开始会让一个孩子拿不到苹果。公式中乘法先算再除以 \(2\),不要先用整数除法计算 (n + 1) / 2。
自测
输入:1 → 预期输出:1
输入:8 → 预期输出:36
方法一:循环累加
方法二:等差数列求和公式
1082:求小数的某一位¶
提示 1:回到竖式除法
1除以8,算出第一位1之后,下一步接着用的是余数2。
第10000位,可不是多输出几位小数就行了。
这题咱们接着用余数往下算。
提示 2:每轮只算一位
digit是“数位”的意思,这里用来存当前算出的一位数字,值是0到9。
a、b、位数n已读入,余数从r = a % b开始:
重复n轮,只输出最后一次digit;除尽后r为0,后面的位会怎样?
知识补充
例如 \(1\div8\):余数 \(1\) 乘 \(10\),商 \(1\) 余 \(2\);余数 \(2\) 乘 \(10\),商 \(2\) 余 \(4\);余数 \(4\) 乘 \(10\),商 \(5\) 余 \(0\)。所以小数是 \(0.125\),再往后都是 \(0\)。
常见错误
不要用浮点数先算出整个小数再截取,第 \(10000\) 位远超浮点精度。余数变成 \(0\) 后,后面的每一位都是 \(0\)。
自测
输入:1 8 3 → 预期输出:5
输入:1 2 10000 → 预期输出:0
自己试试:分别选一个能除尽的分数、一个有循环小数的分数。
自己指定要查的位数,先用竖式或循环规律写下那个数字。
参考代码
1083:计算星期几¶
提示 1:星期只看除7的余数
从星期日开始过7天又回到星期日,必须算出完整\(a^b\)吗?
常见错误
数学的 \(a^b\) 不能写成 C++ 的 a ^ b。不要先求完整幂再取余,大整数溢出后再 % 7 不能补救。
自测
输入:2 3 → 预期输出:Monday
输入:7 1 → 预期输出:Sunday
方法一:分支输出星期
#include <bits/stdc++.h>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
a %= 7;
int t = 1;
for (int i = 0; i < b; i++) {
t *= a;
t %= 7;
}
if (t == 0) cout << "Sunday";
if (t == 1) cout << "Monday";
if (t == 2) cout << "Tuesday";
if (t == 3) cout << "Wednesday";
if (t == 4) cout << "Thursday";
if (t == 5) cout << "Friday";
if (t == 6) cout << "Saturday";
return 0;
}
方法二:数组查表(拓展)
1084:幂的末尾¶
提示 1:先确定要留哪三位
末三位等于对1000取余,但数值8要显示8还是008?
常见错误
不能漏掉 000、008 的前导零;也不能以为 long long 能装下任意大的幂。
自测
输入:2 3 → 预期输出:008
输入:10 3 → 预期输出:000
输入:2 10 → 预期输出:024
自己试试:自己选底数与指数,让结果末三位不足三位。
还能造出末三位全为零的情况吗?
参考代码
第二节 while语句¶
2020:【例4.5】第几项¶
提示 1:超过,还是刚好达到
1加2刚好等于3,题目要求“超过3”,此时能停吗?
提示 2:项号先加一,再累加
long long sum = 0, n = 0,输入上限m后:
退出时n就是哪一项?用m为3手走一遍。
常见错误
“超过”是严格大于,等于还要继续。用 \(m=3\) 检查,应到第 \(3\) 项才超过。
自测
输入:3 → 预期输出:3
输入:1000 → 预期输出:45
自己试试:自己选一个项号,先手算前面所有项的和,把这个和作为输入。
题目要求“超过”,程序应该停在你选的项号吗?
参考代码
2021:【例4.6】最大公约数¶
提示 1:公约数从哪里向下找
12和18的公约数能大于12吗?
方法一从较小值往下试,第一个符合的就是最大值。
提示 2:两个余数都要为0
m、n已读入,自己补i从min(m,n)递减的循环;辗转相除方法在后面作对照。
常见错误
枚举从 max(m, n) 开始会多做无用检查。辗转相除时先保存余数,再交换两数,不能丢掉旧的 b。
自测
输入:12 18 → 预期输出:6
输入:7 7 → 预期输出:7
输入:8 9 → 预期输出:1
自己试试:先选一个希望得到的公约数,再构造两个数。
怎样确认它确实是最大公约数,而不只是其中一个公约数?
方法一:从大到小枚举
方法二:辗转相除
2022:【例4.7】最小n值¶
提示 1:恰好达到就能停
1、二分之一、三分之一逐项加,这题要求总和至少为m。
m为1时,加完第一项要继续吗?
提示 2:未达到时才加入下一项
sum用double初值0,n = 0:
先增加n再除;和1087比较,循环条件为什么差一个等号?
常见错误
不要照搬 1087 的严格大于条件。\(m=1\) 时,本题第一项已经达到,应输出 \(1\)。
自测
输入:1 → 预期输出:1
输入:2 → 预期输出:4
参考代码
2023:【例4.8】数据统计¶
提示 1:没给个数,就边读边数
每次成功读到x,更新最大、最小、总和、个数四份记录。
提示 2:第一项用来定两个最值
总和sum、个数n初值为0,放在while (cin >> x)里:
再补求和与n加一;全是负数时,为什么不能让最大值一直从0比较?
提示 3:输入结束后再算均值
用1.0 * sum / n保留小数,按最小、最大、均值顺序输出。
终端里最后还要结束输入,操作见下面知识补充。
知识补充
回车只是换行,不代表输入结束。
输入完最后一个数,先按回车到新行。
Windows:按 Ctrl+Z,再按回车。
macOS:按 Control+D。
常见错误
不要把第一个数当成个数 \(n\)。
程序还在等待时,先检查是否已经结束输入。
自测
输入:1 2 3 → 预期输出:1 3 2.000
输入:-5 → 预期输出:-5 -5 -5.000
自己试试:自己写几组数,分别把最小值、最大值放在开头和末尾。
每组先手算均值,输入完后记得结束输入。
参考代码
1085:球弹跳高度的计算¶
提示 1:画出前两次落地的路程
第一次只向下走,第二次落地之前还走了上、下两段。
第十次落地后的反弹,要算进已走的总路程吗?
提示 2:第一次单列,后面加往返
初始高度h已读入,先令sum = h,第2到10次落地每轮:
循环后再把h减半,才是第十次反弹高度;自己检查两次输出的含义。
常见错误
总路程不包含第十次落地后的向上路程;不能把这次反弹高度也加进去。拿 \(h=1\) 手算前两次,核对循环次数。
参考代码
1086:角谷猜想¶
提示 1:先把一条等式写完整
从3开始,第一步是3*3+1=10,旧数3不能在输出前丢掉。
提示 2:输出后才更新
n用long long,奇数分支:
仿照补偶数除2,循环条件是n != 1,结束后输出End。
常见错误
不能把 \(1\) 继续变成 \(4\)。不要只按初始输入的范围选择类型,过程中的数可能更大。
自测
输入:1,预期输出:
输入:3,预期输出:
自己试试:自己选一个经过多次变化才到 \(1\) 的小整数。
把每一步等式写在纸上,逐行核对,而不只看最后的 End。
参考代码
1087:级数求和¶
提示 1:相等时仍要继续
本题总和要严格大于k,和2022的“至少达到”不同。
用k为1检查:第一项加完能停吗?
提示 2:把等号留在继续条件里
double sum = 0、int n = 0:
先在纸上走到第一次超过,再核对输出的项号。
常见错误
循环条件不能写成 sum < k。用 \(k=1\) 检查,第一项等于 \(1\),要加第二项才超过。
自测
输入:1 → 预期输出:2
输入:2 → 预期输出:4
参考代码
1088:分离整数的各个数¶
提示 1:每次只拿最右一位
120先输出0,去掉个位后剩12,再输出哪一位?
常见错误
不要把取余 % 10 和除法 / 10 写反。原数末尾的零也要输出,例如 \(120\) 要先输出 \(0\)。
自测
输入:120 → 预期输出:0 2 1
输入:1 → 预期输出:1
参考代码
1089:数字反转¶
提示 1:反转结果是一个整数
120反转成21,末尾零不需要保留成前导零;负号仍在前面。
常见错误
不要把反转理解成逐字符倒序输出,否则 \(120\) 会输出 021,负号也会跑到末尾。\(n=0\) 时答案仍是 \(0\)。
自测
输入:-380 → 预期输出:-83
输入:120 → 预期输出:21
输入:0 → 预期输出:0
自己试试:自己选一个末尾有零的负整数,再预测反转结果。
你希望这组数据同时检查哪两个地方?
参考代码
1090:含k个3的数¶
提示 1:两件事都要满足
数出k个3,还没完呢,能不能被19整除,也得检查。
先把原数m留下,用副本x拆位。
提示 2:拆一位就检查一位
x = m、cnt = 0:
结束后用m % 19 == 0 && cnt == k判断,别拿已经拆完的x去判整除。
常见错误
“恰好”不能写成 >= k。拆位会改变数值,不能等原数已经除成 \(0\) 才拿它判断整除。
自测
输入:43833 3 → 预期输出:YES
输入:39331 3 → 预期输出:NO
输入:43833 2 → 预期输出:NO
自己试试:先写一个恰好含指定个数 \(3\) 的数。
它是否还能被 \(19\) 整除,需要怎样检查?
参考代码
第三节 do-while语句¶
第四节 循环嵌套¶
2026:【例4.12】阶乘和¶
提示 1:每项先求阶乘,再加到和里
先写\(1!+2!+3!\),总和与单项阶乘是同一个变量吗?
提示 2:内层乘积每项重来
外层i从1到n,当前阶乘先令t = 1:
总和s从0开始,放在外层循环外;把t也放外面不重置会发生什么?
常见错误
累加总和从 \(0\) 开始,阶乘从 \(1\) 开始。两层循环的方法里,每次开始计算新阶乘时,要把乘积重新设成 \(1\)。
自测
输入:3 → 预期输出:9
输入:5 → 预期输出:153
输入:10 → 预期输出:4037913
自己试试:选一个较小的 \(n\),分别手算 \(n!\) 和前 \(n\) 项阶乘和。
这两个结果能帮你分辨程序漏加了哪一项吗?
方法一:两层循环求阶乘和
方法二:利用上一项阶乘
2027:【例4.13】三角形¶
提示 1:一行要打几个星号
行号从1开始,第1、2、3行分别有几个星号?
提示 2:内层管字符,外层管换行
固定第i行:
外面再让i从1到n,换行应放在内层循环里还是外?
常见错误
换行应放在内层循环结束之后。放到内层会每个星号占一行;忘记换行则全部挤在一起。
参考代码
2028:【例4.14】百钱买百鸡¶
提示 1:钱和只数要同时凑齐
公鸡x只、母鸡y只、小鸡z只,总共100只。
三只小鸡一元,小鸡数量先按3的倍数试。
常见错误
小鸡三只一元,不能直接用整数 z / 3 代替准确价格而不检查 z % 3 == 0。允许某一种数量为 \(0\),不要擅自从 \(1\) 开始。
方法一:三层枚举
方法二:由总数确定小鸡数量
2029:【例4.15】水仙花数¶
提示 1:先验一个熟悉的水仙花数
把153拆成1、5、3,三次方之和是多少?
提示 2:逐个三位数检查
i从100到999,分别取出百位a、十位b、个位c:
自己补取位表达式;这里不能把三次方写成a ^ 3。
常见错误
题目要求输出符合条件的数,不是只输出个数。立方写成 a * a * a,不能写 a ^ 3。
参考代码
2030:【例4.16】找素数¶
提示 1:找不到因数才说明是质数
先拿9试一下,除以2没除尽。
这就能说它是质数了吗?再拿3试试。
提示 2:遇到因数就否定
候选数i至少为2,先设prime = true,试除数j从2开始:
试到j <= i / j即可;把除数范围补进内层循环。
提示 3:每换一个候选数重新设标记
prime要在每个新i开始时重设;试除结束后再决定输出。
用区间1到4,检查1是否被错误输出。
常见错误
\(1\) 不是素数。检查约数时必须包含平方根,否则 \(4\)、\(9\) 会被误判;每个新候选数都要重新设置标记。
参考代码
2031:【例4.17】四位完全平方数¶
提示 1:先认出两组重复数字
四位数的千位等于百位,十位等于个位;先拿7744试一下。
提示 2:重复数位与平方都要通过
当前候选为i,先比较数位,符合后:
为什么不能只看sqrt能算出一个小数就认定是完全平方数?
常见错误
不能只看 sqrt(i) 能否赋给整数,赋值本来就会截去小数;必须再乘回去检查。十位与个位可以是 \(0\),不要自行排除。
自测
无需输入,预期输出:7744
方法一:枚举四位数
方法二:枚举平方根
2032:【例4.18】分解质因数¶
提示 1:同一质因数可能除好几次
12除以2得6,6还能再除2,不能只判断一次。
提示 2:除尽当前因数再换下一个
当前因数i从2递增,原式左边先输出原来的n:
第一次因数前不加星号,后面才加;为什么这里需要while而不是if?
知识补充
以 \(12\) 为例:先除以 \(2\) 剩 \(6\),再除以 \(2\) 剩 \(3\),最后的 \(3\) 是质数,因此 \(12=2\times2\times3\)。
常见错误
这里输出的是全部质因子,不是 1098 的较大质因子。用质数输入检查剩余因子的处理,用 \(8\) 检查重复因子。
自测
输入:12 → 预期输出:12=2*2*3
输入:13 → 预期输出:13=13
输入:8 → 预期输出:8=2*2*2
自己试试:分别用一个质数和一个质数的幂作输入,数值要在题目范围内。
这两类数据各能检查分解过程中的哪一步?
方法一:从小到大反复试除
方法二:只试到剩余数的平方根
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
bool flag = false;
printf("%d=", n);
for (int i = 2; i <= n / i; i++) {
while (n % i == 0) {
if (flag) printf("*");
printf("%d", i);
n /= i;
flag = true;
}
}
if (n > 1) {
if (flag) printf("*");
printf("%d", n);
}
return 0;
}
2033:【例4.19】阶乘之和¶
提示 1:只保留最后六位
不必求出巨大的阶乘,乘积和总和都可以在每步对一百万取余。
提示 2:当前阶乘和总和分别更新
常量MOD = 1000000,先令long long t = 1、ret = 0:
这两句顺序能交换吗?先用前三项试一遍。
知识补充:为什么只算到25
\(25!\)中,5、10、15、20各提供一个因子5,25提供两个,共有6个。
因子2更多,配对后至少有6个10,因此\(25!\)是\(10^6\)的倍数。
后面的阶乘也都是\(10^6\)的倍数,不再改变最后六位。
常见错误
不要先让整数溢出,再取余。也不要把“末六位”直接理解成固定宽度输出,样例 \(n=10\) 的答案是 37913。
自测
输入:10 → 预期输出:37913
输入:3 → 预期输出:9
输入:25 → 预期输出:940313
自己试试:自己选一对相邻的合法项数,让新增的阶乘是 \(10^6\) 的倍数。
先预测两次输出是否相同,再运行。
参考代码
1091:求阶乘的和¶
提示 1:新阶乘能接着上一个乘
已算出\(3!\),求\(4!\)还要从1重乘吗?
常见错误
先更新阶乘,再加入总和。顺序反了就会少算最后一项;只输出 t 则变成了求阶乘。
自测
输入:2 → 预期输出:3
输入:5 → 预期输出:153
输入:11 → 预期输出:43954713
参考代码
1092:求出e的值¶
提示 1:后一项等于前一项除几
先写1、1、二分之一、六分之一,分别对应从0阶乘开始的倒数。
常见错误
开头的 \(1\) 不能漏掉。若计算阶乘再取倒数,必须留意类型;直接维护 double 的倒数更省事。
自测
输入:0 → 预期输出:1.0000000000
输入:10 → 预期输出:2.7182818011
自己试试:自己选相邻的两个合法项数。
两次结果在保留十位小数之前,应当相差哪一项?
参考代码
1093:计算多项式的值¶
提示 1:幂次从常数项开始
多项式\(1+x+x^2+\cdots+x^n\)一共有几项?
常数项1先放入sum,当前幂t也从1开始。
常见错误
不要每一项都从头乘一次,也不要漏掉常数项 \(1\)。输入顺序是 x、n;数学乘方不能写成 ^。
自测
输入:2.0 4 → 预期输出:31.00
输入:1.0 3 → 预期输出:4.00
自己试试:在题目范围内取 \(x=1\),自己选一个项数。
不逐项乘方,能直接写出预期结果吗?
参考代码
1094:与7无关的数¶
提示 1:两种与7有关的数都排除
14不含数字7,但能被7整除;17含7却不能被7整除。
这两种都不能加平方。
提示 2:在本题范围内拆十位个位
i从1到n,按本题的数据范围检查:
sum从0开始;如果将来范围扩到三位数,这个数位判断还能照搬吗?
常见错误
三个“无关”条件用 &&,不是 ||。例如 \(17\) 不被 \(7\) 整除,但含有数字 \(7\),仍然不能计入。
自测
输入:7 → 预期输出:91
输入:8 → 预期输出:155
自己试试:自己找一个含数字 \(7\)、却不能被 \(7\) 整除的两位数。
用它作上限时,怎样确认程序没有把它的平方加进去?
方法一:直接检查十位和个位
方法二:循环检查各个数位
1095:数1的个数¶
提示 1:一个数里可能有两个1
11给答案贡献1次还是2次?
枚举整数之后,还需要逐位检查。
提示 2:拆副本,不改外层编号
外层i从1到n,让x = i:
cnt在所有循环外从0开始;为什么不能直接反复除i?
常见错误
拆位不能直接修改外层循环变量 i。遇到 \(11\) 后只加一次,会把“出现次数”算成“整数个数”。
自测
输入:1 → 预期输出:1
输入:11 → 预期输出:4
自己试试:自己找一个含有两个数字 \(1\) 的数,并用它作上限。
与上限少 \(1\) 的结果比较,计数应增加多少?
参考代码
1096:数字统计¶
提示 1:区间端点也要计入
区间20到22,先分别数三个整数里的数字2。
常见错误
左右端点都要包含。每个整数可能贡献多个 \(2\),不能找到一个就 break。
自测
输入:2 2 → 预期输出:1
输入:20 22 → 预期输出:4
自己试试:自己选一个含有多个数字 \(2\) 的合法整数,让左右端点都等于它。
这时数的是整数个数,还是数字出现次数?
参考代码
1097:画矩形¶
提示 1:先画出空心矩形的边
用3行4列画一遍,哪些位置一定要输出字符?
只有一行或一列时,还有内部空格吗?
提示 2:每格只选择一种字符
行i从1到n、列j从1到m,已读绘图字符c和标志flag:
每行结束换行,别在每个字符后换行。
常见错误
非零表示实心,不能只认 flag == 1。空心内部应输出空格,不能跳过位置;高和宽也不要颠倒。
自测
输入:3 4 * 0,预期输出:
输入:3 3 # 2,预期输出:
自己试试:自己选择题目允许的最小高度和宽度,画出空心矩形。
再保持尺寸不变,改成实心,哪些位置应该变化?
参考代码
1098:质因数分解¶
提示 1:两个质数乘积,先找小的
77由7和11相乘,若从2开始试,第一个因数是哪一个?
题面保证是两个不同质数的乘积,不必处理一般合数。
提示 2:找到一个,另一个用除法求
n已读入,i从2试到i <= n / i:
为什么此时输出的是较大的那个质因数?
常见错误
不要输出第一个除数本身,题目要较大的质因子。这里能直接输出商,是因为题面保证恰好是两个不同质数的乘积,不能随意推广到所有合数。
自测
输入:6 → 预期输出:3
输入:77 → 预期输出:11
自己试试:自己选两个不同的小质数,相乘作为输入。
打乱你选择它们的先后顺序,预期输出会改变吗?
参考代码
1099:第n小的质数¶
提示 1:名次不是数值
第5个质数是11,不是数字5;先列前五个质数。
提示 2:试到一个质数才增加名次
候选x从2开始,cnt从0开始;先通过试除判断prime。
它外面需要不断增加候选数的循环,换一个数,就重新设一次prime,别把上一个数的判断带过来。
常见错误
\(1\) 不是质数。试除要包含平方根;每个候选数开始时,都要重置质数标记。
自测
输入:1 → 预期输出:2
输入:5 → 预期输出:11
输入:10000 → 预期输出:104729
自己试试:自己列出前几个质数,选两个相邻的名次作输入。
它们的差总是 \(1\) 吗,这能检查程序的哪种误解?
参考代码
1100:金币¶
提示 1:发几枚和发几天一起变
先写前六天收入:1、2、2、3、3、3。
第i组每天发i枚,一共持续几天?
提示 2:按组发,但到天数就停
n表示还剩几天,sum从0开始;第i组可写:
外层让i从1增加;内层为什么还要检查n?
常见错误
第 \(i\) 组是每天发 \(i\) 枚,发 \(i\) 天,不是整组只发 \(i\) 枚。最后一组可能只发一部分,用第 \(4\) 天检查。
自测
输入:1 → 预期输出:1
输入:4 → 预期输出:8
输入:6 → 预期输出:14
自己试试:先找一组金币刚好发完的那一天。
自己测试它的前一天和后一天,分别手算会少发、多发多少枚。
参考代码
1101:不定方程求解¶
提示 1:零也可以是一组解的坐标
对\(2x+3y=6\),把x为0时的情况也列出来。
x、y要求非负,枚举起点不要写成1。
常见错误
非负包括 \(0\),不能从 \(1\) 开始。第二种方法必须控制 x <= c / a,保证剩余量非负;不用再枚举 \(y\)。
自测
输入:2 3 6 → 预期输出:2
输入:4 6 5 → 预期输出:0
输入:1 1 3 → 预期输出:4
自己试试:先选合法的 \(a,b\) 和一对非负整数 \(x,y\),算出正整数 \(c\)。
这能保证至少有一组解,但你怎样找出可能遗漏的其他解?