2. A+B (I)
成绩 10 开启时间 2020年09月7日 星期一 09:00 折扣 0.8 折扣时间 2020年09月15日 星期二 09:00 允许迟交 否 关闭时间 2020年10月10日 星期六 23:00 Description
学过《计算机科学导论》的你应该熟悉“二进制运算”吧?
和十进制不同的是:二进制运算“逢二进一”。下面举一个二进制加法的运算实例:
11101 + 110 -------- 100011
下面请你模拟这个过程。
Input
第一行输入一个正整数 T 表示接下来有 T 组数据。
接下来 T 行,分别是两个待相加的二进制串a、b,长度小于10e5。Output
对于每组数据,请按模拟二进制加法,按题目描述的格式输出正确的运算结果,注意换行,没有多余的空格和换行。
这个题就是简单的二进制加法实现,主要是几个方式方法要用对,不然处理起来你会觉得很麻烦。以下算法很简洁易懂,都是一些常规操作(如果你第一次见,那你就给我点赞!)
1、处理输入
为了方便输入输出,我们将输入的两个二进制串用字符串a、b储存,最终的结果也用字符串ans储存。
或许你在思考的时候会遇到很多问题:比如要从最低位加起,我怎么找到最低位(最低位在字符串的最后一个)开始?最后如果有进位怎么办,该如何预先确定最后结果的位数?计算的时候怎么保证每一位对齐相加?
好了,或许你可以试试这样的思路,让你的困惑烟消云散:我们把字符串a、b倒序储存(这让我们就可以从第1位直接开始算了,也不用考虑对齐的问题),然后处理相加得到结果,最后再将结果翻转回来。
比如题中的用例:
- 11101 + 110,那么我们储存字符串 a = "11101"、b = "110"
- 将字符串a、b翻转:a = "10111"、b = "011"
- 计算ans = a + b = "110001"
- 将结果翻转:ans = "100011"就是真实的结果
至于字符串如何翻转(倒序储存),你可以手动实现,或者直接用c++的一个库函数。reverse()函数在 algorithm 库中,在命名空间std 内(我说这个主要是让你注意在代码最开始加上库和命名空间)。对于一个数组 a 翻转其下标 [start , end) 部分,应该调用函数 reverse(a + start, a+ end)。(如果你的c语言基础很好的话,你会发现,其实参数就是在传入首尾两个地址,不清白也没关系,先用着吧,你以后就会豁然开朗的)
2、相加的实现
这个算法主要是和计算机里的 “全加器” (好奇可以百度)原理一样,不过你自己也很好想到。
我们肯定是一位位加的,对于每一位的相加我们需要计算:相加的结果位(储存到对应的ans)、进位carry。这里主要是进位carry的使用和更新,我们需要把进位carry带入下一位的计算之中,第一位计算之前carry应初始化为0。比如我们看看这个具体的例子:
下面还有一些细节注意处理以下:
🍳首先我们要知道,两个二进制串的长度不一定一样。所以当某一个字符串的位已经加完而另一个没加完,那我们应该默认已经加完的那些位为0一直相加,知道两个串的每一位都处理完。比如:处理11101 + 110 的时候,我们应该是11101 + 00110这样实现的。
🍳然后我们要注意到,我们为了方便输入输出数字都是用字符串储存的,但是我们在计算的时候要将字符转换为数字计算,计算的结果需要再转回字符储存入ans。
3、处理输出
输出的话把规律找好就行了,主要注意在字符串数组的末尾加上"\0",不然printf函数无法识别出你输出的结束点,很有可能会输出一部分乱码...
我们先要找到整个输出的宽度 = 最后结果串长度 + 2,然后根据这宽度依次推算出每行要补齐多少空格来输出就好了。
我为了代码简洁写了两个输出的函数:
/* 输出num个空格 */
void printBlank(int num) {
for(int i = 0; i < num; i++)
printf(" ");
}
/* 输出num个'-',加上换行 */
void printLine(int num) {
for(int i = 0; i < num; i++)
printf("-");
printf("\n");
}
代码实现
还有一个要注意哈,我们定义a、b、ans数组都是在主函数外,也就是定义成全局变量。因为主函数内的栈内存比全局内存要少,如果一些大的内存申请定义在主函数内很容易爆栈,也就是爆内存。你提交上去会出现tle(其实本质应该是mle)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LEN 100005
using namespace std; //声明c++程序的命名空间
/* a、b、ans分别储存输入的两个加数和最后的结果
* 这里用的是字符串数组,为了方便输入输出
* 但是在加法计算的时候要将字符转化为数字 */
char a[LEN], b[LEN], ans[LEN];
/* 输出num个空格 */
void printBlank(int num) {
for(int i = 0; i < num; i++)
printf(" ");
}
/* 输出num个'-',加上换行 */
void printLine(int num) {
for(int i = 0; i < num; i++)
printf("-");
printf("\n");
}
int main() {
int T;
/* 循环处理T次输入 */
for (scanf("%d", &T); T; T--) {
/* 处理输入的过程 */
scanf("%s %s", a, b);
int len1 = strlen(a);
int len2 = strlen(b);
//为了方便计算,我们反转数组,即将我们的个位放到最首、然后十位、百位...
reverse(a, a + len1);
reverse(b, b + len2);
/* 计算过程 */
int bit = 0; //从个位(下标为 0)开始计算
int carry = 0; //记录进位,初始为 0
while (bit < len1 && bit < len2) {
int sum = a[bit] + b[bit] + carry - 2 * '0'; //计算两位之和,记住加上进位(将字符转化为数字!)
ans[bit] = sum % 2 + '0'; //计算该位的结果(将数字转化为字符)
carry = sum / 2; //计算新的进位
bit++;
}
//或许第一个数更长,没加完
while (bit < len1) {
int sum = a[bit] + carry - '0'; //计算两位之和,此时b该位数始终为0
ans[bit] = sum % 2 + '0'; //计算该位的结果(记住加上进位)
carry = sum / 2; //计算新的进位
bit++;
}
//或许第二个数更长,没加完
while (bit < len2) {
int sum = b[bit] + carry - '0'; //计算两位之和,此时b该位数始终为0
ans[bit] = sum % 2 + '0'; //计算该位的结果(记住加上进位)
carry = sum / 2; //计算新的进位
bit++;
}
//还留有进位
if (carry != 0) {
ans[bit++] = carry + '0';
}
ans[bit] = '\0'; //现在bit即为结果ans的位数
/* 输出的过程 */
//将结果反转回来
reverse(a, a + len1);
reverse(b, b + len2);
reverse(ans, ans + bit);
int len = bit + 2; //最终输出的总宽度
//输出第一个数
printBlank(len - len1);
printf("%s\n", a);
//输出第二个数(最前面有加号)
printf("+");
printBlank(len - len2 - 1);
printf("%s\n", b);
//输出横线
printLine(len);
//输出结果
printBlank(2);
printf("%s\n", ans);
}
}
欢迎关注个人公众号“ 鸡翅编程 ”,这里是认真且乖巧的码农一枚。
---- 做最乖巧的博客er,做最扎实的程序员 ----
旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~