16.4.1 计数、求和与求乘积的结合应用
在实际问题中,我们经常需要同时使用计数、求和和求乘积这三种操作。下面我们通过一系列简单的实验来综合应用这些操作。
16.4.2 实验一:计算并统计1~n中偶数的总和及个数
题目描述: 输入一个正整数n,计算并统计1到n中所有偶数的总和及个数。
样例输入:
10
样例输出:
30
5
代码实现:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0; // 总和变量初始化为0
int cnt = 0; // 计数变量初始化为0
int i = 2; // 从第一个偶数2开始循环
while(i <= n) { // 循环到n
sum += i; // 加到总和中
cnt++; // 计数加1
i += 2; // 增加2,保持i为偶数
}
cout << sum << endl; // 输出总和
cout << cnt; // 输出个数
return 0;
}
思考:
- 对于n=10,有哪些偶数?它们的总和和个数是多少?
- 为什么循环从i=2开始,而不是从i=1开始?
解释:
- 1到10中的偶数有:2, 4, 6, 8, 10,共5个,总和为2+4+6+8+10=30。
- 循环从2开始是因为2是第一个偶数,并且通过每次加2,可以直接遍历所有偶数,不需要判断奇偶性。
16.4.3 实验二:计算并统计1~n中与7无关的数的总和及个数
题目描述: 输入一个正整数n(n<9999),计算并输出1到n中与7无关的数的总和及个数。与7无关的数是指既不包含数字7也不是7的倍数的数。
样例输入:
20
样例输出:
174
16
代码实现(方法一):
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0; // 总和变量初始化为0
int cnt = 0; // 计数变量初始化为0
int i = 1; // 从1开始循环
while(i <= n) { // 循环到n
// 提取各位数字
int g = i % 10; // 个位
int s = i / 10 % 10; // 十位
int b = i / 100 % 10; // 百位
int q = i / 1000 % 10; // 千位
// 判断是否与7无关
if(g != 7 && s != 7 && b != 7 && q != 7 && i % 7 != 0) {
sum += i; // 加到总和中
cnt++; // 计数加1
}
i++; // 循环变量加1
}
cout << sum << endl; // 输出总和
cout << cnt; // 输出个数
return 0;
}
代码实现(方法二):
#include <iostream>
using namespace std;
int main() {
int n; // 定义变量n
cin >> n; // 输入整数n
int sum = 0; // 总和变量初始化为0
int cnt = 0; // 计数变量初始化为0
int i = 1; // 从1开始循环
while(i <= n) { // 循环到n
// 提取各位数字
int g = i % 10; // 个位
int s = i / 10 % 10; // 十位
int b = i / 100 % 10; // 百位
int q = i / 1000 % 10; // 千位
// 使用逻辑非判断是否与7无关
if(!(g == 7 || s == 7 || b == 7 || q == 7 || i % 7 == 0)) {
sum += i; // 加到总和中
cnt++; // 计数加1
}
i++; // 循环变量加1
}
cout << sum << endl; // 输出总和
cout << cnt; // 输出个数
return 0;
}
代码实现(方法三):
#include <iostream>
using namespace std;
int main() {
int n; // 定义变量n
cin >> n; // 输入整数n
int sum_z = 0; // 所有数的总和
int sum = 0; // 与7有关的数的总和
int cnt = 0; // 与7有关的数的个数
int i = 1; // 从1开始循环
while(i <= n) { // 循环到n
sum_z += i; // 累加所有数的总和
// 提取各位数字
int g = i % 10; // 个位
int s = i / 10 % 10; // 十位
int b = i / 100 % 10; // 百位
int q = i / 1000 % 10; // 千位
// 判断是否与7有关
if(g == 7 || s == 7 || b == 7 || q == 7 || i % 7 == 0) {
sum += i; // 加到与7有关的总和中
cnt++; // 与7有关的计数加1
}
i++; // 循环变量加1
}
cout << (sum_z - sum) << endl; // 输出与7无关的数的总和
cout << (n - cnt); // 输出与7无关的数的个数
return 0;
}
思考:
- 什么是与7无关的数?这三种方法如何实现这个判断?
- 方法一和方法二在逻辑表达上有什么区别?
- 方法三为什么要先计算所有数的总和和与7有关的数的总和?
- 哪种方法在代码复杂度和执行效率上更有优势?
解释:
- 与7无关的数是指既不包含数字7也不是7的倍数的数。方法一使用多个与(&&)条件直接判断所有位数不是7且不能被7整除;方法二使用逻辑非(!)和逻辑或(||)的组合间接判断;方法三则通过计算所有与7有关的数,然后用总体减去这部分来获得结果。
- 方法一和方法二表达的是同样的逻辑,但使用了不同的表达方式。方法一使用了多个与(&&)条件:
g != 7 && s != 7 && b != 7 && q != 7 && i % 7 != 0
,直接表述”各位都不是7且不是7的倍数”;方法二使用了逻辑非(!)和逻辑或(||)的组合:!(g == 7 || s == 7 || b == 7 || q == 7 || i % 7 == 0)
,表述”不是(任一位是7或是7的倍数)”,根据德摩根定律,这两种表达是等价的。 - 方法三采用了一种间接思路:先计算1到n的所有数的总和(sum_z)和总个数(n),再计算与7有关的数的总和(sum)和个数(cnt),最后通过相减得到与7无关的数的总和(sum_z-sum)和个数(n-cnt)。这种方法展示了解决问题的另一种思维方式:”计算补集再相减”,有时比直接计算目标集合更简单高效。
- 从代码复杂度看,方法二略优于方法一,因为表达更简洁;从执行效率角度,三种方法的时间复杂度都是O(n),但方法三需要额外的加法操作来计算所有数的总和,理论上效率略低。不过,方法三思路独特,在某些情况下(比如已知所有数的总和时)可能会更有优势。
总之,这三种方法展示了同一问题的不同解决思路,体现了计算机思维的多样性和灵活性。方法一直观易懂,方法二代码简洁,方法三思路独特,各有特点。
16.4.4 实验三:计算并统计m~n中3的倍数的个数和总和
题目描述: 输入两个正整数m和n(保证m <= n),计算并输出m到n之间(包括m和n)所有3的倍数的个数和总和。
样例输入:
1 10
样例输出:
3
18
代码实现:
#include <iostream>
using namespace std;
int main() {
int m, n;
cin >> m >> n;
// 找到第一个大于等于m的3的倍数
int start = m;
if(start % 3 != 0) {
start = start + (3 - start % 3);
}
int sum = 0; // 总和变量初始化为0
int cnt = 0; // 计数变量初始化为0
int i = start; // 从第一个3的倍数开始
while(i <= n) { // 循环到n
sum += i; // 加到总和中
cnt++; // 计数加1
i += 3; // 增加3,保持i为3的倍数
}
cout << cnt << endl; // 输出个数
cout << sum; // 输出总和
return 0;
}
思考:
- 对于样例输入1 10,3的倍数有哪些?它们的总和和个数是多少?
- 代码如何找到第一个大于等于m的3的倍数?
解释:
- 1到10中的3的倍数有:3, 6, 9,共3个,总和为3+6+9=18。
- 通过计算start % 3的余数,然后加上(3 – 余数),就能找到下一个3的倍数。
16.4.5 实验四:计算并统计m~n中奇数和偶数的个数和总和
题目描述: 输入两个正整数m和n(保证m <= n),分别计算并输出m到n之间(包括m和n)所有奇数和偶数的个数和总和。
样例输入:
1 10
样例输出:
5 25
5 30
代码实现:
#include <iostream>
using namespace std;
int main() {
int m, n;
cin >> m >> n;
int odd_sum = 0; // 奇数总和
int odd_cnt = 0; // 奇数个数
int even_sum = 0; // 偶数总和
int even_cnt = 0; // 偶数个数
int i = m; // 从m开始循环
while(i <= n) { // 循环到n
if(i % 2 == 0) { // 如果i是偶数
even_sum += i; // 加到偶数总和
even_cnt++; // 偶数计数加1
} else { // 如果i是奇数
odd_sum += i; // 加到奇数总和
odd_cnt++; // 奇数计数加1
}
i++; // 循环变量加1
}
cout << odd_cnt << " " << odd_sum << endl; // 输出奇数个数和总和
cout << even_cnt << " " << even_sum; // 输出偶数个数和总和
return 0;
}
思考:
- 对于样例输入1 10,奇数和偶数分别有哪些?
- 如何优化这个算法,避免在循环中判断奇偶性?
解释:
- 1到10中的奇数有:1, 3, 5, 7, 9,共5个,总和为25;偶数有:2, 4, 6, 8, 10,共5个,总和为30。
- 可以分别从第一个奇数和第一个偶数开始,使用两个循环,每次增加2,分别遍历所有奇数和偶数。
16.4.6 实验五:计算并统计某范围内能被3或5整除的数
题目描述: 输入两个正整数L和R(保证L <= R),计算并输出L到R之间(包括L和R)能被3整除或能被5整除的数的个数和总和。
样例输入:
1 20
样例输出:
10
98
代码实现:
#include <iostream>
using namespace std;
int main() {
int L, R;
cin >> L >> R;
int sum = 0; // 总和变量初始化为0
int cnt = 0; // 计数变量初始化为0
int i = L; // 从L开始循环
while(i <= R) { // 循环到R
// 判断是否能被3整除或能被5整除
if(i % 3 == 0 || i % 5 == 0) {
sum += i; // 加到总和中
cnt++; // 计数加1
}
i++; // 循环变量加1
}
cout << cnt << endl; // 输出个数
cout << sum; // 输出总和
return 0;
}
思考:
- 对于样例输入1 20,哪些数能被3整除或能被5整除?
- 如果一个数同时能被3和5整除,会被计算几次?
解释:
- 能被3整除的数有:3, 6, 9, 12, 15, 18;能被5整除的数有:5, 10, 15, 20。去重后共10个数,总和为3+5+6+9+10+12+15+18+20=98。
- 如果一个数同时能被3和5整除(如15),在我们的算法中只会被计算一次,因为我们使用的是逻辑或(||)操作。
16.4.7 实验六:计算并统计某范围内数字的平方和与立方和
题目描述: 输入两个正整数x和y(保证x <= y),计算并输出x到y之间(包括x和y)所有数的平方和与立方和。
样例输入:
1 3
样例输出:
14
36
代码实现:
#include <iostream>
using namespace std;
int main() {
int x, y;
cin >> x >> y;
int sum2 = 0; // 平方和
int sum3 = 0; // 立方和
int i = x; // 从x开始循环
while(i <= y) { // 循环到y
sum2 += i * i; // 加上i的平方
sum3 += i * i * i; // 加上i的立方
i++; // 循环变量加1
}
cout << sum2 << endl; // 输出平方和
cout << sum3; // 输出立方和
return 0;
}
思考:
- 对于样例输入1 3,各数的平方和立方分别是多少?
- 这个问题是否可以使用数学公式直接计算?
解释:
- 1到3的平方和为1²+2²+3²=1+4+9=14,立方和为1³+2³+3³=1+8+27=36。
- 是的,平方和可以用公式n(n+1)(2n+1)/6计算,立方和可以用公式[n(n+1)/2]²计算。但这里我们有范围x到y,需要进行适当调整。
16.4.8 实验七:综合计数、求和与求乘积
题目描述: 输入两个正整数A和B(保证A <= B且B <= 20),计算并输出以下值:
- A到B之间(包括A和B)能被3整除的数的个数
- A到B之间能被3整除的数的总和
- A到B之间能被3整除的数的乘积
样例输入:
1 10
样例输出:
3
18
162
代码实现:
#include <iostream>
using namespace std;
int main() {
int A, B;
cin >> A >> B;
int cnt = 0; // 计数变量初始化为0
int sum = 0; // 总和变量初始化为0
int prod = 1; // 乘积变量初始化为1
int i = A; // 从A开始循环
while(i <= B) { // 循环到B
// 判断是否能被3整除
if(i % 3 == 0) {
cnt++; // 计数加1
sum += i; // 加到总和中
prod *= i; // 乘到乘积中
}
i++; // 循环变量加1
}
cout << cnt << endl; // 输出个数
cout << sum << endl; // 输出总和
cout << prod; // 输出乘积
return 0;
}
思考:
- 对于样例输入1 10,能被3整除的数有哪些?
- 如果范围内没有能被3整除的数,乘积的结果应该是什么?
解释:
- 1到10中能被3整除的数有:3, 6, 9,共3个数,总和为18,乘积为3×6×9=162。
- 如果范围内没有能被3整除的数,乘积应该保持初始值1。
16.4.9 实验八:简单的综合应用
题目描述: 输入三个正整数m、n和k(保证m <= n且k > 0),计算并输出m到n之间(包括m和n)能被k整除的数的以下信息:
- 能被k整除的数的个数
- 能被k整除的数的总和
- 能被k整除的数的乘积
样例输入:
1 10 2
样例输出:
5
30
3840
代码实现:
#include <iostream>
using namespace std;
int main() {
int m, n, k;
cin >> m >> n >> k;
int cnt = 0; // 计数变量初始化为0
int sum = 0; // 总和变量初始化为0
int prod = 1; // 乘积变量初始化为1
int i = m; // 从m开始循环
while(i <= n) { // 循环到n
// 判断是否能被k整除
if(i % k == 0) {
cnt++; // 计数加1
sum += i; // 加到总和中
prod *= i; // 乘到乘积中
}
i++; // 循环变量加1
}
cout << cnt << endl; // 输出个数
cout << sum << endl; // 输出总和
cout << prod; // 输出乘积
return 0;
}
思考:
- 对于样例输入1 10 2,能被2整除的数有哪些?
- 如何优化这个算法,减少循环次数?
解释:
- 1到10中能被2整除的数有:2, 4, 6, 8, 10,共5个数,总和为30,乘积为2×4×6×8×10=3840。
- 可以从第一个能被k整除的数开始,每次增加k,直接遍历所有能被k整除的数。