OJ(getline自定义分隔符)

这篇博客介绍如何使用getline函数处理以逗号为分隔符的电话公司客户数据,查询指定客户的姓名和电话。文章通过示例说明了输入输出格式,并提供了实现思路,包括使用getline读取姓名,cin提取电话,并使用cin.get()消除缓冲区的换行符。
描述

输入是某电话公司的若干客户姓名及电话号码,中间用逗号分隔,然后是若干要查询的客户姓名,输出是这些查询的客户姓名及其电话。

输入

第一行是一个正整数n(不超过100000),表示某电话公式的客户个数,然后是n个客户,每个客户占一行,由一个字符串构成的姓名和一个电话号码组成,中间用逗号分隔,每个姓名的长度不超过45。

接下来是一个正整数m,然后是m个需要查询的客户姓名,每个姓名占一行。

输出

对于每个查询的客户,如果该客户是电话公式的客户,则显示其姓名和电话号码,如果不存在,则显示姓名和‘No’,中间用‘:’分隔。每个输出占一行。

输入样例

4
Bob,34778654
Ana,567894833
Alice,3456789
Gates,6893838
5
Alice
bob
Gate
Bob
Ana

输出样例

Alice:3456789
bob:No
Gate:No
Bob:34778654
Ana:567894833

思路

getline时以’,’为分隔符,将姓名提取出来,再用cin提取电话,此时缓冲区末尾还有一个换行符,需要用cin.get()去掉。

代码
#include "stdafx.h"
#include <iostream>
#include <map>
<think> 根据问题描述,我们需要解决的是多组大整数求和的问题。由于整数可能超过200位,所以我们不能使用普通的整型变量来存储,需要使用字符串或数组来模拟大整数的加法。 输入格式: - 有多组测试数据。 - 每组数据第一行是一个正整数n,表示后面要输入的大整数的个数。 - 接下来n行,每行一个不超过200位的大整数。 - 当n=0时,结束输入。 输出格式: - 对于每组数据,输出这n个大整数的和,每个和单独占一行。 约束条件: - 代码长度不超过16KB(这通常不是问题,因为我们的代码不会很长)。 - 时间限制400ms,内存限制64MB,栈限制8192KB。 由于大整数的位数最多200位,n没有给出具体范围,但是考虑到时间限制400ms,我们需要高效的算法。大整数加法的时间复杂度与位数和个数有关,因此我们可以逐组处理,每组内将n个大整数相加。 实现思路: 1. 使用字符串来存储每个大整数。 2. 对于每组数据,读取n,如果n为0则退出。 3. 读取n个大整数,使用一个字符串来保存当前的和(初始为空字符串)。 4. 然后依次将每个大整数加到和字符串上。 5. 大整数相加的算法:将两个大整数(字符串)从低位到高位逐位相加,注意进位。为了便于操作,我们可以将字符串反转(即从低位开始对齐),然后逐位相加,最后再将结果反转回来。 但是,需要注意的是,我们可能有多个大整数相加,所以我们可以采用累加的方式:初始和设为"0",然后依次将每个大整数加到和上。 另一种更高效的方法:因为n可能比较大,我们也可以使用一个数组(或字符串)来存储最终的和,然后同时处理所有大整数的相同位。但由于每个大整数的位数可能不同,我们可以先统一为相同长度(前面补0),然后从最低位到最高位逐位求和并进位。这种方法适合一次性加多个数,但在n不确定的情况下,我们可以采用累加的方式。 考虑到代码简洁性和时间效率,我们采用累加的方式。因为每个大整数最多200位,n虽然可能很大,但大整数加法的操作次数为O(数字位数),而数字位数最大200,所以每次加法操作最多200次,那么n次加法最多200*n次操作。题目没有给出n的最大值,但时间限制400ms,所以我们需要考虑n的最大值。 假设最坏情况,n的最大值可能是多少?题目没有说明,但我们可以从内存限制来估计:每组数据有n个不超过200位的字符串,每个字符串长度最多201(包括换行符?但题目说输入格式是每行一个整数,所以就是字符串形式)。内存64MB,假设n最大为10000,那么10000*201=2.01MB,内存是足够的。 时间上,每次加法最多200位,那么10000次加法就是200*10000=2e6次操作,这在400ms内是完全可以接受的(C++每秒大约可以处理1e8次简单操作)。 因此,我们采用累加的方式:用一个字符串存储当前和,然后依次将每个大整数与这个和相加。 具体的大整数加法函数(两个字符串相加): - 将两个字符串反转(或者从末尾开始逐位相加),从低位到高位。 - 相同位置的数字字符转为数字相加,再加上进位,然后计算新的进位和当前位的数字。 - 最后如果进位不为0,则要加上进位。 - 注意处理两个字符串长度不一致的情况。 另外,为了效率,我们可以不反转字符串,而是从字符串的末尾开始向前处理。但为了清晰,我们可以写一个函数,将两个字符串表示的大整数相加,返回结果字符串。 例如: ```cpp string add(string num1, string num2) { // 从右往左逐位相加 int i = num1.size() - 1, j = num2.size() - 1; int carry = 0; string result = ""; while (i >= 0 || j >= 0 || carry) { int sum = carry; if (i >= 0) { sum += num1[i] - '0'; i--; } if (j >= 0) { sum += num2[j] - '0'; j--; } carry = sum / 10; result.push_back('0' + (sum % 10)); } // 此时result是倒序的,需要反转 reverse(result.begin(), result.end()); return result; } ``` 但是,注意题目中可能有前导零?但输入保证是整数,所以可能有前导零吗?题目没有说,但整数通常不会有前导零(除非是0本身)。但我们的加法函数可以处理前导零,因为我们在相加时是按数字相加,最后反转,可能会在结果前面产生0?比如两个0相加?我们需要考虑结果可能是多个0的情况,但通常我们输出时不能有前导零(除了0本身)。 但是,我们上面的加法函数在计算"0"+"0"时,会得到"00",但我们希望输出"0"。因此,我们可以在返回前检查一下结果字符串,去除前导零,但要注意如果结果就是0,则要保留一个0。 改进:在返回结果前,去除前导零(但要注意不要去除到空字符串,如果全零则保留一个0)。 修改加法函数: ```cpp string add(string num1, string num2) { // 如果有一个是空字符串,则返回另一个 if (num1 == "") return num2; if (num2 == "") return num1; int i = num1.size() - 1, j = num2.size() - 1; int carry = 0; string result = ""; while (i >= 0 || j >= 0 || carry) { int sum = carry; if (i >= 0) { sum += num1[i] - '0'; i--; } if (j >= 0) { sum += num2[j] - '0'; j--; } carry = sum / 10; result.push_back('0' + (sum % 10)); } reverse(result.begin(), result.end()); // 去除前导零 // 如果结果字符串开头有多个0,需要去掉,但保证至少有一个0 if (result[0] == '0') { int k = 0; while (k < result.size() && result[k] == '0') { k++; } if (k == result.size()) { result = "0"; } else { result = result.substr(k); } } return result; } ``` 但是,这里有一个问题:我们是在反转之后去除前导零,而反转后,前导零变成了在字符串开头?实际上,我们的相加过程中,结果字符串是倒序的(低位在前),反转后变成高位在前,然后我们去除的是高位的0。所以这个逻辑正确。 但是,上面的去除前导零的代码可能在数字开头有多个0时会被去除,但注意,我们相加的结果开头可能有多个0。比如"0001"和"0002"相加,我们得到的结果是"0003",然后去除前导零变成"3"。这符合要求。 但是,注意如果结果为0,比如输入两个0,那么结果可能是多个0,经过去除后变成"0",正确。 然而,这个去除前导零的逻辑在结果全0的时候会保留一个0,正确。 但是,我们也可以优化:在相加的过程中,如果两个数都是0,那么我们会在结果中产生多个0吗?不会,因为我们在去除前导零时会把连续的0去掉并保留一个0。 但是,上面的去除前导零的代码有一个小问题:当结果字符串开头只有一个0时,比如"0",那么k=0,第一个字符就是0,然后k++变成1,然后k==1<1不成立?等等,k==1,而result.size()为1,所以k==1等于size,所以进入if,设置result="0"。所以正确。 但是,还有一种情况:结果字符串开头没有0,那么我们就不应该去除。上面的代码中,我们检查第一个字符是否为0,如果是才去除。否则直接返回。 但是,考虑效率:我们每次加法都要去除前导零,而前导零可能很少出现,特别是当两个数都是很大的数时,结果不会出现前导零。所以我们可以优化:只在最后需要输出的时候去除一次前导零?但是不行,因为我们还要继续加后面的数,如果中间结果有前导零,那么在进行下一次加法时,字符串长度会更大,会影响效率。所以我们还是需要去除前导零,但我们可以尝试避免产生前导零。实际上,在加法过程中,我们按数字相加,最高位产生进位才会增加位数,否则不会。所以中间结果可能产生前导零吗?不会,因为我们的加法函数每次返回的都是去除前导零的结果。所以初始和设为"0",然后加第一个数,第一个数可能是"0001",但我们输入时会直接读到字符串,可能是"0001",然后我们相加时,两个字符串相加,得到"1"(因为去除前导零)。所以我们需要在加法函数中去除前导零。 另一种做法:在输入时,我们就把每个大整数的前导零去掉?但题目没有说输入是否有前导零。考虑到题目描述是“大整数”,通常输入不会有前导零,但为安全起见,我们可以在读取每个大整数后去除其前导零(但要注意全0的情况)。或者由加法函数来处理。 我们决定在加法函数中去除前导零。 但是,上面的加法函数在去除前导零时,如果结果字符串很长,去除前导零会遍历整个字符串,这可能会影响效率(特别是有很多次加法时)。因此,我们可以优化去除前导零的方法:在反转后,我们只检查开头连续0的个数,然后截取,这样时间复杂度是O(连续0的个数),不会很高。 现在,考虑主程序逻辑: - 使用循环读取n,直到n为0。 - 对于每组数据,读取n个大整数,然后依次加到总和中。 - 初始总和设为"0"(字符串)。 - 逐个相加,然后输出结果(每组输出一行)。 注意:输入输出使用C++的iostream或cstdio。由于输入可能比较大,使用cin和cout可能较慢,可以考虑使用scanf和printf。但是,大整数是字符串形式,所以我们可以用cin读取字符串,或者用scanf读取字符串。题目没有说输入输出量,但为了速度,我们使用scanf和printf,并且关闭cin同步。 由于我们使用C++,而且输入输出字符串,使用scanf和printf是高效的。但是,我们也可以使用cin,但需要关闭同步。 代码结构: ```cpp #include <iostream> #include <cstdio> #include <string> #include <algorithm> #include <cctype> using namespace std; // 大整数加法函数 string add(string num1, string num2) { // 如果有一个为空,则返回另一个 if (num1 == "0") return num2; if (num2 == "0") return num1; // 这里优化:如果有一个为0,则直接返回另一个,避免后续计算 // 但是要注意,我们输入的数字可能是"0",但我们已经去除了前导零,所以如果为0就会变成"0" // 所以我们可以先检查这种情况 if (num1 == "0") return num2; if (num2 == "0") return num1; // 但如果两个都是0,那么后面会处理,所以这里可以写,也可以不写(因为后面的代码也能处理) // 为了效率,我们加上,避免后续的循环 if (num1 == "0" && num2 == "0") return "0"; // 通常情况 int i = num1.size()-1, j = num2.size()-1; int carry = 0; string res = ""; while (i>=0 || j>=0 || carry) { int d1 = (i>=0) ? num1[i--]-'0' : 0; int d2 = (j>=0) ? num2[j--]-'0' : 0; int sum = d1 + d2 + carry; carry = sum / 10; res += (sum % 10) + '0'; } reverse(res.begin(), res.end()); // 去除前导零 // 由于两个非零数相加,可能开头有多个0?不太可能,因为进位最多一个,所以除非连续进位,否则不会出现多个0?但我们在相加过程中,如果两个数前面都是0,那么我们相加后前面会产生0?不会,因为我们是倒序相加,然后反转,所以前面应该是没有0的,除非是进位后产生了0?不会,进位后至少是1。 // 但是,我们之前已经处理了0的情况,所以这里其实可以不用去除前导零?不对,因为两个数相加,如果都是0,我们已经返回"0",所以这里不会执行。但如果不是0,那么相加的结果开头一定不会是0?不一定,比如"0001"和"0002"?但是我们传入的num1和num2是已经去除前导零的了吗?注意,我们传入的num1和num2可能是上一次加法返回的结果,而我们在加法函数返回前已经去除了前导零,所以传入的num1和num2不会有前导零(除了可能是"0")。所以这里得到的res反转后,开头一定不为0(除非结果为0,但结果为0的情况我们已经用前面的特判覆盖了?不对,两个非零数相加也可能得到0吗?不可能,至少是大于等于最小的那个数。所以我们可以保证,这里得到的res反转后,开头一定不为0?不对,考虑两个数:比如"0"和"0"我们已经特判,那么非0数相加,开头一定不会是0。所以这里可以不用去除前导零? // 但是,我们一开始没有对传入的字符串去除前导零(虽然我们调用add函数时,传入的是上一次的结果,而结果已经去除前导零,以及输入的数字我们并没有去除前导零),所以我们需要保证传给add函数的字符串都是去除前导零的?或者我们在add函数内部统一处理。 // 为了安全起见,我们还是在函数开始时去除传入字符串的前导零?或者我们在外部保证传入的字符串已经去除前导零?这样效率更高。因为外部输入时,我们可以去除每个输入的前导零。 // 因此,我们修改策略:在读取每个大整数后,先去除前导零(但注意全0保留一个0)。这样,传入add函数的两个字符串都是规范格式(无前导零,除非是"0")。 // 那么,在add函数中,我们就不需要最后去除前导零了?因为两个规范格式的字符串相加,结果字符串反转后,开头只可能是非0或者进位后非0?不对,比如5+5,结果是"01"反转后是"10",开头是1,没有0。所以不会有前导零。 // 但是,也有可能:比如两个长度为1的数相加,进位后变成两位数,开头不会是0。所以可以保证。 // 因此,我们可以在add函数中省略去除前导零的步骤。但要注意,我们之前已经处理了0的情况,所以剩下的情况相加的结果开头不会为0。 // 但是,考虑一个特殊情况:比如"0"和"1"相加,我们已经在前面特判了,所以会直接返回"1",从而跳过相加步骤。所以不会执行到反转那一步。 // 所以,我们不需要在add函数中最后去除前导零。但是,我们前面的特判是针对字符串为"0"的情况,那么如果传入的是"000"(没有去除前导零),那么我们的特判不会触发(因为字符串是"000"而不是"0"),所以我们需要在外部去除前导零。 // 因此,我们需要在读取每个大整数后去除前导零,也在每次加法后保证结果是规范格式(通过我们的特判)。但我们在add函数中并没有对传入的字符串去除前导零,所以如果我们传入的是"000",那么我们的特判(num1=="0")不会触发,因为"000"!="0"。所以我们需要在外部去除输入的前导零。 // 我们将去除前导零的操作提取为一个函数,用于规范字符串。 return res; } ``` 因此,我们重新设计: 1. 写一个函数去除字符串的前导零:`string removeLeadingZeros(string s)` 2. 在读取每个大整数后,用这个函数去除前导零。 3. 在add函数中,我们假设传入的字符串是规范格式(即已经去除前导零,且不为空,或者为"0")。 同时,在add函数中,我们先判断两个数中是否有0,如果有0,则直接返回另一个数(规范格式,所以0就是"0")。如果都是0,返回"0"。 然后,再进行加法操作。这样,加法操作后得到的结果反转后开头一定不会是0(因为两个非0数相加,和至少大于0,而进位最多使位数增加1,所以反转后开头不为0),所以我们不需要再去除前导零。 但是,注意:如果两个数相加后,进位为0,并且结果是0?不可能,因为两个规范格式的非0数相加,和至少大于0。所以我们的add函数可以这样写: ```cpp string add(string num1, string num2) { // 先去除传入参数的前导零?但是为了保证,我们可以在函数开头调用removeLeadingZeros?但这样每次调用都会扫描两个字符串,效率较低。所以我们在外部保证传入的是规范格式。 // 因此,我们假设传入的是规范格式。 if (num1 == "0") return num2; if (num2 == "0") return num1; // 接下来,两个数都不为0 int i = num1.size()-1, j = num2.size()-1; int carry = 0; string res = ""; while (i>=0 || j>=0 || carry) { int d1 = (i>=0) ? num1[i--]-'0' : 0; int d2 = (j>=0) ? num2[j--]-'0' : 0; int sum = d1 + d2 + carry; carry = sum / 10; res += (sum % 10) + '0'; } reverse(res.begin(), res.end()); return res; } ``` 但是,这个函数在num1和num2都是规范格式时,返回的也是规范格式(没有前导零)。因为如果两个规范格式的非0数相加,结果的开头一定不是0。 因此,我们还需要一个去除前导零的函数,用于输入: ```cpp string removeLeadingZeros(string s) { // 如果字符串为空,返回空 if (s.empty()) return "0"; int start = 0; while (start < s.size()-1 && s[start]=='0') { // 注意,保留最后一个0,所以是s.size()-1 start++; } // 如果全部是0,那么start指向最后一个0,我们保留一个0 return s.substr(start); } ``` 但是,上面的函数有误:如果字符串是"000",那么start会变成2(因为s.size()=3,start最多到2,因为start<3-1=2不满足,所以跳出循环,然后返回s.substr(2)="0"),正确。 但如果是"0",那么start=0,然后循环条件:start<0(不满足),所以返回s.substr(0)="0",正确。 如果是"1",那么start=0,s[0]='1',不进循环,返回"1",正确。 如果是"0001",那么start=0,循环中start会增加到3(因为s[0],s[1],s[2]都是0,而s[3]是1,循环条件:start<4-1=3,所以当start=3时,循环条件为3<3不成立,跳出。然后返回s.substr(3)="1",正确。 但是,循环条件应该是:`while (start < s.size() && s[start]=='0')`,但是我们只需要去掉前导零,如果后面还有非零,那么我们需要保留。所以循环应该是:只要当前字符是0,且还没有到最后一个字符(或者不管后面是什么,只要是0就去掉)?但要注意,我们允许最后一个是0(如果整个字符串都是0,那么最后保留一个0),所以循环条件应该是:`while (start < s.size()-1 && s[start]=='0')`,这样当字符串为"000"时,start会停在最后一个0的前一个位置?不对,我们想保留最后一个0,所以我们应该在遇到第一个非0时停止,并且如果全是0,则保留最后一个0。 正确的做法:找到第一个非0字符,如果找不到,则返回"0"。所以: ```cpp string removeLeadingZeros(string s) { int start = 0; while (start < s.size() && s[start]=='0') { start++; } if (start == s.size()) { return "0"; } return s.substr(start); } ``` 这样,对于"000",start会增加到3(因为s.size()=3,start=0,1,2时都是0,然后start=3,等于size,返回"0"。 对于"0001",start增加到3,然后返回s.substr(3)="1"。 对于"0",start=0,循环后start=1(因为循环条件:start<1不满足,所以循环一次也不执行?不对,循环条件:start<1(s.size()=1)为true,然后s[0]=='0',所以start++变成1,然后循环条件:1<1为false,退出循环。然后start=1,等于size,所以返回"0"。正确。 对于"1",循环条件:start=0<1为true,但s[0]=='1'为false,所以不进入循环,然后start=0,不等于size,所以返回s.substr(0)="1"。 所以这个函数正确。 但是,注意输入可能有负号?题目说整数,而且是非负整数吗?题目没有说明,但通常大整数是非负整数。如果有负数,那么题目应该说明,这里我们假设全部是非负整数。 因此,主程序: 1. 读取n。 2. 当n不等于0时,循环: a. 初始化一个字符串sum="0"。 b. 循环n次: i. 读取一个字符串str。 ii. 去除前导零:str = removeLeadingZeros(str); iii. 将str加到sum上:sum = add(sum, str); c. 输出sum(每组输出一行)。 但是,注意:我们可能需要处理空行?题目没有说输入有空行,所以按正常输入。 另外,由于输入输出量可能很大,我们使用scanf和printf,但C++中字符串用%s读取,但大整数可能有200位,所以我们可以定义一个字符数组,然后转成string。 例如: ```cpp char s[210]; // 200位,加上结束符,210足够 scanf("%s", s); string str = s; ``` 或者用cin,但为了速度,我们用scanf。 最后,注意n=0时退出。 但是,题目要求n=0结束输入,所以我们在读取n之后,如果n==0,则break。 代码整体结构: ```cpp #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <string> using namespace std; string removeLeadingZeros(string s) { int start = 0; while (start < s.size() && s[start] == '0') { start++; } if (start == s.size()) { return "0"; } return s.substr(start); } string add(string num1, string num2) { // 假设传入的num1和num2都是规范格式(即去除前导零的,除非是0) if (num1 == "0") return num2; if (num2 == "0") return num1; int i = num1.size() - 1; int j = num2.size() - 1; int carry = 0; string res = ""; while (i >= 0 || j >= 0 || carry) { int d1 = (i >= 0) ? num1[i] - '0' : 0; int d2 = (j >= 0) ? num2[j] - '0' : 0; i--; j--; int sum = d1 + d2 + carry; carry = sum / 10; res += (sum % 10) + '0'; } reverse(res.begin(), res.end()); return res; } int main() { int n; char temp[210]; // 用于读取大整数的临时字符数组 while (scanf("%d", &n) != EOF) { if (n == 0) break; string sum = "0"; for (int i = 0; i < n; i++) { scanf("%s", temp); string str = temp; str = removeLeadingZeros(str); sum = add(sum, str); } printf("%s\n", sum.c_str()); } return 0; } ``` 但是,这个程序有一个问题:如果输入的数字很大(比如200位),那么add函数中,两个200位的数相加,结果最多201位,所以没有问题。但是,我们使用的是递归相加(实际上是迭代),因为每个大整数加一次,所以总共有n次加法。但是,每次加法需要O(max(len1, len2))的时间。最坏情况下,n最大是多少?(题目没有给出,但内存限制64MB,栈限制8192KB,所以n不会太大,但时间限制400ms)。 另外,我们每次加法都会创建一个新的字符串,并且进行反转操作,反转操作的时间复杂度是O(len)。所以总的时间复杂度是O(n*L),其中L是平均位数(最大200)。 但是,如果n很大,比如100000(10万),那么每次加法200次操作(因为位数最多200),则总操作次数为100000*200=20e6,这在400ms内应该可以(C++每秒大约1e8次操作)。 但是,我们使用scanf和printf,输入输出效率较高。 但是,注意:我们每次加法都需要反转字符串,而字符串长度最多201,反转操作是O(201),常数时间,所以总时间还是O(n*200)。 另外,我们使用字符串拼接,`res += (sum % 10) + '0';` 这个操作在C++中,字符串追加字符,平均时间复杂度是O(1)(因为预留空间),但最坏情况可能每次追加都要重新分配内存,但这里最多201次,所以不会太慢。 我们可以通过预先分配空间来优化:`res.reserve(210);` 在add函数中,一开始就预留210字节。 另外,注意:我们每组数据开始前,sum设为"0",所以每组数据独立。 测试:输入n=2,然后输入两个数,比如"123"和"456",输出"579"。 但是,输入"0000"和"0000",规范后变成"0"和"0",然后add("0","0")返回"0",正确。 输入n=0,结束。 但是,有一个潜在问题:我们使用scanf读取整数n,然后读取n个字符串。但是,输入可能有多个空格?题目没有明确,但题目说输入格式:每组数据两行:第一行n,第二行是n个整数?不对,题目描述是:输入有多组数据。每组数据第一行是正整数n,然后第二行?不对,引用[2]中描述是:输入有两行:第一行是一个正整数n,第二行是n个整数。但是,题目要求是:每组先输入整数n,再输入n个不超过200位的大整数。所以,每组输入为:一行n,然后n行(每行一个大整数)。 但是,我们上面的代码中,输入n之后,循环n次,每次读取一行(一个字符串)。这样符合题意。 注意:题目要求“再输入n个不超过200位的大整数”,所以每个大整数占一行?还是第二行是n个整数在同一行?引用[2]中描述:第二行是n个整数(同一行)。但题目描述是:“每组先输入整数n,再输入n个不超过200位的大整数”,没有说明是否在同一行。再看引用[2]的问题描述:输入格式是第二行是n个整数,所以同一行有n个整数?但题目又说“输入有多组数据。每组数据两行”。 这里存在歧义。我们需要明确:输入格式是每组两行:第一行一个整数n,第二行有n个整数(空格分隔)?还是第一行n,然后接下来n行,每行一个整数? 查看用户的问题描述:“每组先输入整数n,再输入n个不超过200位的大整数”,这里“n个”大整数,没有说明在同一行还是n行。但用户的问题描述中引用了引用[2],而引用[2]中描述是“第二行是n个整数”,所以是同一行。 因此,我们需要改变输入方式:每组数据的第一行是n,第二行是n个用空格分隔的大整数(字符串)?还是第二行是n个字符串连续写在一起?题目没有说,但引用[2]说“第二行是n个整数”,所以是空格分隔。 但是,大整数可能有前导空格?或者空格分隔,所以我们可以按空格分隔读取。 因此,我们需要读取一行,然后按空格分割成n个字符串。 但注意,大整数可能有200位,所以一行可能有200*n + (n-1)个字符(空格分隔),最多200000个字符?所以我们需要用getline读取一行,然后分割。 修改:读取n之后,需要读取一行,然后忽略换行符?因为接下来要读取第二行。但是,我们读取n之后,换行符还在缓冲区。所以我们可以先读取换行符,再读取第二行。 或者,读取n后,用getchar()读取换行符,然后用gets或getline读取一行。 但是,为了避免缓冲区问题,我们读取n后,用getline读取一行(即第二行),然后分割这一行。 步骤: 1. 读取n。 2. 如果n==0,break。 3. 读取第二行:使用getline(cin, line) 或者 fgets。注意,我们使用了scanf读取n,后面用getline可能会出错(因为scanf会留下换行符)。所以我们可以: - 用scanf读取n后,用getchar()读取换行符,再用fgets读取一行?或者用cin.getline。 - 或者统一用cin,但关闭同步。 为了简单,我们可以使用cin,并且关闭同步。 修改:使用cin,关闭同步。 代码: ```cpp #include <iostream> #include <string> #include <vector> #include <algorithm> #include <cctype> #include <sstream> using namespace std; string removeLeadingZeros(string s) { int start = 0; while (start < s.size() && s[start] == '0') { start++; } if (start == s.size()) { return "0"; } return s.substr(start); } string add(string num1, string num2) { if (num1 == "0") return num2; if (num2 == "0") return num1; int i = num1.size() - 1; int j = num2.size() - 1; int carry = 0; string res = ""; while (i >= 0 || j >= 0 || carry) { int d1 = (i >= 0) ? num1[i] - '0' : 0; int d2 = (j >= 0) ? num2[j] - '0' : 0; i--; j--; int sum = d1 + d2 + carry; carry = sum / 10; res += (sum % 10) + '0'; } reverse(res.begin(), res.end()); return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); int n; while (cin >> n) { if (n == 0) break; // 读取换行符,因为后面要读一整行 string line; getline(cin, line); // 读取n之后的换行符,但是注意:cin>>n后,缓冲区留下'\n',所以getline读到空串。 getline(cin, line); // 读取第二行 // 现在line中包含n个大整数(空格分隔) // 分割line vector<string> nums; stringstream ss(line); string numStr; while (ss >> numStr) { nums.push_back(numStr); } // 但是,可能一行中不足n个?或者多于n个?题目说第二行是n个整数,所以应该有n个 if (nums.size() != n) { // 如果输入错误,我们可以按实际数量处理,但题目保证,所以不考虑 } string sum = "0"; for (int i = 0; i < n; i++) { string str = nums[i]; str = removeLeadingZeros(str); sum = add(sum, str); } cout << sum << endl; } return 0; } ``` 但是,这里有一个问题:题目并没有说大整数之间用空格分隔,也没有说第二行是空格分隔。引用[2]中描述是“第二行是n个整数”,所以应该是空格分隔。但用户的问题描述中引用[2]是同一个问题吗?用户的问题描述中引用了引用[1]和引用[2],但引用[1]是关于C++两个整数和,引用[2]是关于C语言输入格式,但引用[2]中描述是“第二行是n个整数”,所以是空格分隔。 但是,用户的问题描述要求:“每组先输入整数n,再输入n个不超过200位的大整数”,没有明确是否在同一行。但引用[2]是用户提供的参考,所以按照引用[2],是同一行。 因此,我们按照同一行空格分隔处理。 但是,如果输入格式是n行,每行一个整数,那么上面的代码就不对了。所以我们需要确认输入格式。 用户的问题描述中,引用了引用[2]并且说“上述引用仅供参考”,但用户真正需求是:“每组先输入整数n,再输入n个不超过200位的大整数”,没有明确在同一行还是n行。但为了安全,我们可以询问,但这里我们无法询问,所以我们需要考虑两种输入格式。 但用户引用的引用[2]是同一行,我们就按同一行处理。 另外,用户的问题描述中,引用[2]还有一个细节:“输入有多组数据.每组数据两...”,所以每组两行:第一行n,第二行n个整数(空格分隔)。 所以,我们按同一行读取。 但是,我们上面的代码有一个bug:在cin>>n后,缓冲区留下换行符,第一个getline(cin,line)会读取这个换行符,得到一个空字符串。然后第二个getline读取第二行。 所以,我们可以将两个getline合并:在cin>>n后,直接getline(cin,line)读取换行符,然后再getline(cin,line)读取第二行。 或者,在cin>>n后,用cin.ignore()忽略换行符,然后用getline(cin,line)读取第二行。 修改: ```cpp int main() { ios::sync_with_stdio(false); cin.tie(0); int n; while (cin >> n) { if (n == 0) break; cin.ignore(); // 忽略换行符 string line; getline(cin, line); // 分割line vector<string> nums; stringstream ss(line); string numStr; while (ss >> numStr) { nums.push_back(numStr); } if (nums.size() < n) { // 如果不足n个,那么可能还有大整数在下一行?但题目说在同一行,所以假设是n个 // 如果不足,我们只处理nums.size()个 // 但题目保证第二行有n个,所以不考虑 } string sum = "0"; for (int i = 0; i < n; i++) { string str = nums[i]; str = removeLeadingZeros(str); sum = add(sum, str); } cout << sum << endl; } return 0; } ``` 但是,我们也可以不用分割,直接用stringstream逐个读取字符串。 现在,考虑性能:当n很大时,比如100000,而我们用stringstream分割一个长字符串(长度可能200000),分割时间复杂度是O(L)(L为字符串长度),可以接受。 另外,add函数中,字符串相加,我们每次都会生成一个新的字符串,并且进行反转操作。如果n很大,可能会产生很多临时对象。我们可以考虑优化:使用一个数组来存储最终结果的每一位,然后同时相加所有大整数的每一位。这样,我们只需要遍历最大位数次。 例如: - 用一个数组ans[0..maxLen]来存储和的每一位,初始为0。 - 然后,遍历每个大整数,从最低位到最高位,将对应位的数字加到ans数组的相应位置,然后统一进位。 这样,对于一组数据,时间复杂度为O(n * maxLen)(但maxLen是200,所以是O(200*n)),和我们之前一样。但避免了多次字符串反转和临时字符串的创建。 但实现起来稍复杂,且我们目前的代码在400ms内应该可以通过(20e6操作,大约0.2秒),所以先采用当前方法。 最后,我们输出时,不需要再去除前导零,因为add函数返回的字符串已经是规范格式。 但是,考虑一个极端情况:如果n=0,但是我们还没有退出,所以应该在读取n后立即判断。 另外,当n=0时,我们break,所以不会处理。 现在,我们需要测试一组数据: 输入: 2 123 456 1 0 0 输出: 579 0 注意,当输入一个0时,输出0。 另外,测试大数:输入两个200位的9,相加后应该是一个201位的数,第一位是1,后面200位是8。 例如:输入 2 999...9(200个9) 999...9(200个9) 输出: 1999...8(200个9)?不对,是1999...8(199个9)?不对,200个9加200个9,结果是201位:1后面200个0?不对,应该是200个9乘以2=399...98?不对: 999...9 (200个) + 999...9 (200个) = 1999...8 (199个9和一个8) 所以结果应该是1后面199个9,然后一个8?不对,是200个9相加,所以每一位都是18,然后进位。结果是: 最高位有进位,所以第一位是1,然后199位都是9+9=18,然后进位1,所以变成9(因为进位1加上18=19,然后这一位写9,再进位1)?不对,我们是从低位开始加,逐位进位。 所以实际上,和的每一位 = 该位上所有数字的和 + 进位,然后模10,进位=和/10。 所以,两个200位的9相加: - 从最低位开始,最低位:9+9=18,写8,进位1。 - 倒数第二位:9+9+1=19,写9,进位1。 - 倒数第三位:9+9+1=19,写9,进位1。 - ... - 直到最高位:9+9+1=19,写9,进位1。 - 然后进位1,再写一个1。 所以结果是1后面200个9,然后最后一位应该是8?不对,我们写的是从最低位开始,所以最后反转字符串时,我们得到一个字符串:先写的是最低位的8,然后9,9,...,9,然后最高位的9,然后再写最后的进位1。所以反转后,字符串是:1(最高位),然后200个9,然后8(最低位)?不对,反转后,字符串就是:1,9,9,...,9,8。但我们希望是1后面跟着199个9,然后8?不对,应该是1后面200个9?不对,总共201位:第一位是1,然后199个9,然后8?这肯定不对。 正确的结果应该是:1999...98(200个9)。所以一共201位?不对,200位的数相加,最大200位数,和最大201位。 两个200位的数相加,和是201位:1后面200个0?不对,两个999...9(200位)相加=1999...98(199个9和一个8)?不对,200个9加200个9=400...0? 显然不是。 例如:99+99=198,9+9=18,所以200个9+200个9=2*(200个9)=400...0? 不对,99+99=198,999+999=1998,所以200个9+200个9=199...98(199个9和一个8)?不对,应该是199...98(199个9)前面还有一个1,所以总共201位:1, 199个9, 8? 不对,应该是1, 199개9, 8? 但这样是201位:1+199+1=201。 实际上,两个相同的200位数相加,相当于乘以2。200个9组成的数=10^200-1,乘以2=2*10^200-2,这个数表示为1999...998(200-1=199个9,然后98?不对,2*10^200是2后面200个0,减2得到1999...998(199个9和1个8):199...998(199个9和一个8)? 比如2*100=200,减2=198(2后面00,减2=198);2*1000=2000,减2=1998。 所以两个200位的9相加,等于199...98(199个9和一个8),所以是201位数字:1, 199个9, 8 -> 1后面199个9,然后8?所以字符串为"1" + 199个"9" + "8"?不对,应该是"1"然后199个"9"然后"8"?但199+1+1=201位,正确。 但我们的add函数是如何工作的?比如"99"+"99": - 反转?没有,我们是直接从字符串的末尾开始(即低位): num1="99", num2="99" i=1, j=1 d1=9, d2=9, sum=18, carry=1, res+='8' -> res="8" i=0,j=0: d1=9,d2=9,sum=9+9+1=19, carry=1, res+='9' -> res="89" then carry=1, and i,j=-1, so add carry: res+='1' -> res="891" then reverse res -> "198" which is correct. 另一个例子:两个200位的"9"(字符串是200个'9'): 从最低位(第199位)开始加,每次处理一位,连续200次,每次进位1,然后最后一次(最高位)加完后,还有进位1,所以再加一个1。 所以,过程中res字符串先追加200个字符:先是8,然后199个9?不对,第一次:进位0,9+9=18,写8,进位1,res="8" 第二次:9+9+1=19,写9,进位1,res="89" 第三次:9+9+1=19,写9,进位1,res="899" ... 第200次:9+9+1=19,写9,进位1,res="8"后面199个9(因为已经199次写9)?不对,第k次写的位置是第k位(低位)。 然后,第200次后,进位1,再写1 -> res = "8" + 199个"9" + "1" (这里res的长度是201),然后反转后: "1" + 199个"9" + "8" -> 199...98 (199个9和一个8)?不对,反转后,第一个字符是最后写的那个1,然后是199个9,然后是第一个写的8?所以字符串是"1" + 199个"9" + "8",正确。 反转前:res = "8999...91" (第一个是8,中间199个9,最后一个是1)?不对,我们每次加在字符串末尾,所以最低位在字符串的开头。所以res是:先写个位(8),然后十位(9),百位(9),...,最高位(9),然后最后加进位1。所以res = "8" + 199个"9" + "1" (总共201个字符),反转后变成 "1" + 199个"9" + "8"。 所以,字符串为"1"后面199个"9",然后"8",但这不是199...98,而是199...98,因为199个9后面是8,所以是199...98(199个9和一个8),正确。 所以,我们的add函数正确。 因此,我们使用上述代码。 最后,注意栈限制8192KB,我们的代码中,递归调用深度不大(没有递归,都是迭代),所以栈空间不会太大。 内存限制64MB,我们保存字符串,每组数据最多有n个字符串(每个长度最大200,但我们在计算过程中,只保留总和字符串,而总和字符串最多201位,所以内存占用很小。 因此,代码满足要求。 完整代码如下(使用同一行空格分隔的输入格式): </think> 以下是解决多组大整数求和问题的C++代码实现。代码处理多组测试数据,每组先读取整数n,然后读取n个不超过200位的大整数,计算它们的和并输出。当n=0时程序结束,满足代码长度、时间和内存限制要求。 ```cpp #include <iostream> #include <string> #include <vector> #include <algorithm> #include <sstream> using namespace std; // 去除字符串前导零 string removeLeadingZeros(string s) { int start = 0; while (start < s.size() && s[start] == '0') { start++; } if (start == s.size()) { return "0"; } return s.substr(start); } // 大整数加法函数 string add(string num1, string num2) { if (num1 == "0") return num2; if (num2 == "0") return num1; int i = num1.size() - 1; int j = num2.size() - 1; int carry = 0; string res = ""; // 预分配空间提高效率 res.reserve(max(num1.size(), num2.size()) + 1); while (i >= 0 || j >= 0 || carry) { int d1 = (i >= 0) ? num1[i] - '0' : 0; int d2 = (j >= 0) ? num2[j] - '0' : 0; i--; j--; int sum = d1 + d2 + carry; carry = sum / 10; res += (sum % 10) + '0'; } // 反转得到正确顺序 reverse(res.begin(), res.end()); return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); // 提高输入输出效率 int n; while (cin >> n && n != 0) { // 忽略换行符 cin.ignore(); string line; getline(cin, line); // 读取整行数据 // 分割字符串 stringstream ss(line); vector<string> nums; string numStr; while (ss >> numStr) { nums.push_back(numStr); } string sum = "0"; for (int i = 0; i < n; i++) { string cleanStr = removeLeadingZeros(nums[i]); sum = add(sum, cleanStr); } cout << sum << '\n'; } return 0; } ``` ### 代码说明: 1. **输入处理**: - 使用 `cin.ignore()` 清除输入缓冲区中的换行符 - `getline(cin, line)` 读取整行大整数数据 - `stringstream` 分割空格分隔的大整数 2. **大整数处理**: - `removeLeadingZeros` 函数去除输入数字的前导零(如 "000123" → "123") - `add` 函数实现大整数加法: - 从最低位开始逐位相加 - 处理进位 - 反转结果字符串得到正确顺序 3. **性能优化**: - `ios::sync_with_stdio(false)` 和 `cin.tie(0)` 加速输入输出 - `res.reserve()` 预分配字符串空间减少内存重分配 - 时间复杂度 $O(n \times L)$,其中 $L$ 为数字最大位数(≤200) 4. **输出要求**: - 每组数据输出单独一行 - 结果自动去除前导零("0" 保留) - 当 $n=0$ 时程序终止 ### 测试示例: **输入**: ``` 2 000123 456 3 1000000000 9999999999 1 1 0 0 ``` **输出**: ``` 579 11000000000 0 ``` 此代码在 $n \leq 10^5$ 的测试数据下可在 400ms 内完成计算[^1],内存使用远低于 64MB,满足所有约束条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值