







第二题:程序(2)------大整数加法魔法工厂
大整数运算
一、为什么需要这个程序?
同学们,假设我们要计算:
123 + 456
普通的 int 就可以完成。
但是,如果题目要求计算:
123456789123456789123456789
+
987654321987654321987654321
普通的 int 装不下这么大的数字。
这个时候,我们就可以自己动手,使用数组来模拟加法。
这就像建立了一家魔法工厂:
-
每个数组格子存储一位数字。
-
从个位开始计算。
-
每一位超过9,就向前进位。
-
最后从最高位向最低位输出。
本程序正是利用数组实现大整数加法。试卷规定,两个非负整数的位数不超过100000,并且可能包含前导零。
二、程序的完整代码
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int a[100007], b[100007], c[100007], carry[100007];
string input_str;
int a_len, b_len;
int main() {
cin >> input_str;
a_len = input_str.size();
for (int i = 0; i < a_len; i++) {
a[i] = input_str[a_len - i - 1] - '0';
}
cin >> input_str;
b_len = input_str.size();
for (int i = 0; i < b_len; i++) {
b[i] = input_str[b_len - i - 1] - '0';
}
carry[0] = 0;
for (int i = 0; i < max(a_len, b_len) + 1; i++) {
c[i] = a[i] + b[i] + carry[i];
if (c[i] >= 10) {
carry[i + 1] = 1;
c[i] -= 10;
} else {
carry[i + 1] = 0;
}
}
for (int i = max(a_len, b_len); i >= 0; i--) {
cout << c[i];
}
cout << endl;
return 0;
}
三、先理解四个数组
程序使用了四个数组:
int a[100007];
int b[100007];
int c[100007];
int carry[100007];
我们把它们想象成四排储物柜。
| 数组 | 作用 |
|---|---|
a |
保存第一个数字的各位 |
b |
保存第二个数字的各位 |
c |
保存两个数字相加后的各位 |
carry |
保存进位信息 |
最重要的一点是:
数组下标0保存个位,下标1保存十位,下标2保存百位......
这与我们平常从左往右写数字的习惯相反。
例如:
输入数字:123
程序执行:
for (int i = 0; i < a_len; i++) {
a[i] = input_str[a_len - i - 1] - '0';
}
最终:
| 数组下标 | 0 | 1 | 2 |
|---|---|---|---|
a[i] |
3 | 2 | 1 |
| 对应数位 | 个位 | 十位 | 百位 |
为什么要倒着存?
因为加法通常从个位开始计算。
倒着存储后,程序就可以从 i = 0 开始处理个位,再处理十位、百位,非常方便。
四、理解进位:个位超过9怎么办?
我们先看最简单的例子:
7
+ 8
---
15
个位相加得到15。
但是,一个数位只能保存0~9,因此:
-
当前位保存5。
-
向高一位进1。
程序正是这样做的:
c[i] = a[i] + b[i] + carry[i];
if (c[i] >= 10) {
carry[i + 1] = 1;
c[i] -= 10;
} else {
carry[i + 1] = 0;
}
当 c[i] >= 10 时:
carry[i + 1] = 1;
c[i] -= 10;
例如:
c[i] = 15
执行后:
c[i] = 5
carry[i+1] = 1
这就是我们熟悉的竖式加法。
五、第22题:输入123和456,输出是不是0579?
原题:
当输入为:
123 456
程序输出为:
0579
判断是否正确。
1. 数组初始化
输入第一个数123:
a[0] = 3
a[1] = 2
a[2] = 1
输入第二个数456:
b[0] = 6
b[1] = 5
b[2] = 4
初始进位:
carry[0] = 0
2. 第0轮:计算个位
c[0] = a[0] + b[0] + carry[0];
代入:
c[0] = 3 + 6 + 0 = 9
9小于10,不进位:
c[0] = 9
carry[1] = 0
3. 第1轮:计算十位
c[1] = 2 + 5 + 0 = 7
所以:
c[1] = 7
carry[2] = 0
4. 第2轮:计算百位
c[2] = 1 + 4 + 0 = 5
所以:
c[2] = 5
carry[3] = 0
5. 第3轮:多出来的一位
循环条件是:
i < max(a_len, b_len) + 1
两个数都是3位,因此:
i < 4
循环会执行:
i = 0、1、2、3
第3轮时:
a[3] = 0
b[3] = 0
carry[3] = 0
所以:
c[3] = 0
注意,这个0并不是加法结果真正需要的数位,而是程序额外计算出来的最高位。
6. 输出
输出循环:
for (int i = max(a_len, b_len); i >= 0; i--) {
cout << c[i];
}
从 i = 3 开始输出:
c[3] = 0
c[2] = 5
c[1] = 7
c[0] = 9
最终:
0579
答案
√,正确。
侦探笔记
这道题有一个非常重要的细节:
程序没有删除结果最前面的0。
六、第23题:输入不含前导零,输出就一定不含前导零吗?
原题:
假设输入的两个数均不含前导零,则程序输出的结果也一定不会含有前导零。
1. 什么叫前导零?
例如:
0579
最前面的0就是前导零。
而:
579
没有前导零。
2. 输入不含前导零,能保证输出也不含吗?
不能。
我们刚才已经计算过:
123 + 456 = 579
但是程序输出:
0579
因为输出循环总会从:
max(a_len, b_len)
这个下标开始输出。
对于两个3位数来说,它会额外输出 c[3]。
当没有产生最高位进位时,这一位就是0。
答案
×,错误。
侦探笔记
要区分两个概念:
-
数学上的结果: 579。
-
程序实际输出的字符串: 0579。
阅读程序时,不能只算数学结果,还必须严格按照输出语句执行。
七、第24题:删除进位,结果一定比原来小吗?
原题:
将第21行:
c[i] = a[i] + b[i] + carry[i];
改为:
c[i] = a[i] + b[i];
程序输出的结果一定比原来的结果小。
1. 进位有什么作用?
例如:
18
+ 27
----
45
个位:
8 + 7 = 15
个位留下5,向十位进1。
十位:
1 + 2 + 1 = 4
如果把进位去掉,十位就会变成:
1 + 2 = 3
结果就会变成35,而不是45。
2. 但是,题目说的是"一定"
我们不能只找一个会产生进位的例子。
还要考虑没有进位的情况。
例如:
12 + 23 = 35
每一位都没有产生进位。
删除 carry[i] 后,计算结果仍然是35。
因此,修改后的输出不一定比原来小。
答案
×,错误。
侦探笔记
判断题中出现"一定""所有""必然"等词时,应该主动寻找边界情况。
没有进位的加法,就是这道题的重要反例。
八、第25题:输入12345和678,输出什么?
原题:
12345 678
选项:
A. 012923
B. 013023
C. 13023
D. 130230
1. 先算数学结果
12345
+ 678
-------
13023
但不要急着选择C。
我们还要考虑程序的输出方式。
2. 观察数组长度
第一个数有5位:
a_len = 5
第二个数有3位:
b_len = 3
因此:
max(a_len, b_len) = 5
程序输出循环为:
for (int i = 5; i >= 0; i--)
它会输出:
c[5]、c[4]、c[3]、c[2]、c[1]、c[0]
一共6位。
由于结果13023没有超过5位,因此:
c[5] = 0
最后输出:
013023
答案
B. 013023。
九、第26题:把 >= 10 改成 > 10,输入95和15会怎样?
原题:
将:
if (c[i] >= 10)
改为:
if (c[i] > 10)
当输入为:
95 15
输出为:
A. 01010
B. 110
C. 140
D. 1410
1. 先理解修改的影响
原来的条件:
c[i] >= 10
表示只要当前位达到10,就需要进位。
修改后:
c[i] > 10
只有超过10才进位。
因此,恰好等于10时,程序不再进位。
这会导致错误。
2. 第0轮:个位
5 + 5 = 10
修改后的条件:
10 > 10
不成立。
所以:
c[0] = 10
carry[1] = 0
注意:当前位变成了10,已经不是合法的十进制数位。
3. 第1轮:十位
9 + 1 + 0 = 10
仍然不满足:
10 > 10
因此:
c[1] = 10
carry[2] = 0
4. 第2轮:额外的一位
c[2] = 0
5. 输出
从高位到低位输出:
c[2] = 0
c[1] = 10
c[0] = 10
注意,cout << c[i] 输出的是整数10,而不是一个字符。
所以最终字符串是:
01010
答案
A. 01010。
侦探笔记
在进位题中:
c[i] >= 10
和:
c[i] > 10
差别非常大。
恰好等于10时,前者会进位,后者不会。
十、第27题:两个n位正整数相加,输出有什么特点?
原题:
假设输入的两个数均为n位正整数(不含前导零),且它们的和小于 10^n,则程序输出的字符串一定满足:
A. 第一个字符一定不为'0'
B. 长度一定为n
C. 长度一定为n+1,且第一个字符为'0'
D. 长度可能为n+2
1. 数学结果有多少位?
题目告诉我们:
两个数都是n位正整数,并且:
和 < 10^n
所以结果最多只有n位。
例如:
n = 3
两个3位数相加,但结果小于1000。
那么结果可能是:
200
999
不会超过3位。
2. 程序会输出多少位?
程序的输出循环是:
for (int i = max(a_len, b_len); i >= 0; i--)
两个数都是n位,所以:
max(a_len, b_len) = n
程序输出的下标是:
n、n-1、n-2、......、1、0
一共:
n + 1
位。
3. 最前面的字符是什么?
因为两个数的和小于 10^n,所以不会产生真正的第n+1位。
程序额外计算的:
c[n]
就是0。
所以输出字符串一定:
-
长度为n+1。
-
第一个字符为'0'。
答案
C. 长度一定为n+1,且第一个字符为'0'。
第二题总结
| 题号 | 考查内容 | 答案 |
|---|---|---|
| 22 | 数组模拟加法、输出前导零 | √ |
| 23 | 程序输出与数学结果的区别 | × |
| 24 | 进位的重要性、反例 | × |
| 25 | 数组下标、输出位数 | B |
| 26 | 条件边界、进位判断 | A |
| 27 | 循环范围、字符串长度 | C |
第二题通关!