1000–1099题号段机试基础题通关:多组输入与易错点
简介:杭州电子科技大学在线OJ题解代码合集,面向准备算法竞赛、课程实验与在线刷题的编程学习者,覆盖1000至1099号共一百道题目的C/C++实现,可用于对照解题思路、补齐边界处理,并理解常见算法在真实题目中的落地方式。压缩包内共九十个文件,以六十二个cpp和十六个c源码为主体,另含少量VC60工程配置、调试中间文件与可执行文件,整体约1.1MB,体积轻便,适合本地编译验证与按题号检索。已有1840人学习,说明该题解在杭电OJ练习群体中具备一定参考价值。读者可从中获得排序、查找、动态规划、图论、数学推理等题型的完整代码样本,并借助已调试通过的实现观察输入输出格式、边界条件与性能取舍;工程文件也便于在旧版VC环境中直接打开运行,适合作为刷题复盘和算法入门的辅助材料。
1. 开篇:1000 号段的分,比你想象中更容易漏
很多人打开某高校在线评测平台的 1000–1099 题号段代码,扫一眼觉得都是入门题,跳过不练。真正在机试现场丢分最多的地方,恰恰是这一段。前二十题考循环和判断,往后考的是读题能力——多组输入读到什么条件停、每组结果之间要不要空行、中间结果会不会溢出 int。这些跟算法无关,判题机却一分不让。这份题号段代码的用处不是替你想思路,而是把一百道题里反复出现的读入写法、输出写法、边界处理固化成手感。三类人最该刷:第一次上机不知道怎么读数据、逻辑写得对但总被判格式错、以及想在两周内把基础题速度提起来的人。企业笔试和推免机试的第一批题,走的也是同一套路数。
2. 考点地图:1000–1099 题号段的四层难度与拿分顺序
2.1 一百道题其实只有四层,先认清自己卡在哪层
这段题号不是均匀铺开的,它有一条很清晰的爬坡线。前段是语法层,一道题只转一个弯;中段开始上数据结构的雏形;后段已经接近小型模拟题。刷之前先分层,比盲目从 1000 顺着往下做效率高得多。很多人的问题是把时间全花在第一层,反复做已经会的题,一遇到多组输入就卡住,然后就停在那里不动了。
| 层级 | 典型形态 | 拿分要点 | 最常见失分 |
|---|---|---|---|
| 第一层 语法 | 两数运算、判断、单层循环 | 读入读干净,输出不多不少 | 多打一句提示语、末尾多空格 |
| 第二层 多组数据 | 求和、统计、读到结束标志 | 终止条件写准 | 漏最后一组、死循环 |
| 第三层 字符串 | 统计字符、大小写、回文、拆分 | 下标边界和字符分类 | 越界、负数传给字符函数 |
| 第四层 结构与模拟 | 结构体排序、日期、进制、图形 | 比较函数写对、状态每次重算 | 排序比较写<=、闰年表不更新 |
第一层的分是白送的,做对不算本事,做快了才算。第二层是全段的枢纽,卡在这里的人最多,因为判题机不会告诉你「你少读了一组」,它只给你一个 WA。第三层开始出现数组和字符串,越界不一定崩,但结果会错。第四层是拉分项,涉及排序和模拟的题,代码量上去了,出错点也从一处变成五处。
2.2 多组输入:三种终止形态和一行判定的写法
题号段里将近三分之一是「多组数据」题。这类题不看算法,只看你会不会读。常见的有三种终止形态:读到文件结尾、先给组数、读到特定标志(比如两个 0 或一个 0)结束。写错任何一种,样例能过,提交全错。
#include <cstdio> int main() { int a, b; // scanf 返回成功读入的变量个数;读到文件末尾返回 EOF,值是 -1 // 用 != EOF 判断,不要用 while (1) 再在内部 break,容易漏掉第一种写法 while (scanf("%d%d", &a, &b) != EOF) { printf("%d\n", a + b); } return 0; }这段的关键在scanf的返回值。它返回的是「成功匹配并赋值了几个变量」,全部成功是 2,只读到一个是 1,一个都没读到是EOF。所以更稳的写法是while (scanf("%d%d", &a, &b) == 2),因为它同时挡住了「只读到半个输入」这种脏数据情况。
先给组数的题型是另一种节奏,循环次数由第一行决定:
int t; scanf("%d", &t); while (t--) { // t 组数据,t-- 在判断时用的是旧值 int n; scanf("%d", &n); // 每组内部重新初始化累加器,这一步漏了就是连环错 long long sum = 0; for (int i = 0; i < n; ++i) { int x; scanf("%d", &x); sum += x; } printf("%lld\n", sum); }while (t--)是一个能省一行但容易误读的写法,等价于「执行 t 次」。如果读起来别扭,换成for (int i = 0; i < t; ++i)更保险。累加器必须在循环体内重新置零,放在外面就会把上一组的结果带进来,这类错误在提交记录里非常常见。
第三种是「读到 0 0 结束」,判定要写在循环条件里,而不是在循环体末尾判断后break:
while (scanf("%d%d", &a, &b) == 2 && (a != 0 || b != 0)) { printf("%d\n", a + b); }注意中间的||不能写成&&。a != 0 || b != 0的意思是「两个不同时为 0」,而a != 0 && b != 0会把3 0这种合法数据也踢出去。
用 C++ 的cin >>时,cin在while条件里会被转换成布尔值,读到结尾自动为假,写起来更短:while (cin >> a >> b) { ... }。但要注意它默认和scanf是同步的,数据量大时会慢,题号段这点数据量无所谓。
2.3 输入输出格式:空格、换行、空行这三件事
判题机对格式的容忍度比人低。同一个答案,多一个空格就是错。号段里关于格式的约定基本就三条:行内元素之间用一个空格分隔、行末不留空格、多组数据之间是否需要空行要看题目明说。
行末不留空格的常见写法是「第一个元素直接输出,后面的先输出空格再输出元素」,而不是「每个元素后面跟一个空格,最后再想办法删」。后者做起来别扭,还容易在只有一行输出时出错。
// 输出 n 个数,行内空格分隔,行末无空格 for (int i = 0; i < n; ++i) { if (i > 0) printf(" "); // 空格打在元素前面,天然不会有多余的结尾空格 printf("%d", a[i]); } printf("\n");空行是最容易踩的一个。题目如果说「每组输出之间空一行」,正确的做法是先输出第一组,之后每一组都「先打空行再打结果」,或者用「不是第一组就多打一个换行」的判断。写错的方向通常是把换行放在每组末尾,结果最后多出一个空行,判题机照样判错。
浮点数题还有一个隐藏要求:保留几位小数。printf("%.2f\n", x)会自动四舍五入,但整数除法的结果永远是整数,1/2得到 0 而不是 0.5,必须写成1.0/2或显式转换。这个坑在求平均值、求比值的题里出现频率极高。
提示:题目里凡是出现「精确到」「保留」「四舍五入」这类词,先确认变量类型是不是浮点,再确认输出格式串有没有写位数。
3. 高频题型:五类反复出现的题和对应模板
3.1 数值计算:溢出和精度是两个不同的坑
号段里的计算题分两类。一类是常规整型运算,坑在溢出;一类是高精度,坑在不能用内置类型。
先说溢出。int在常见环境下是 32 位,能装到约 21 亿。题号段里凡是让求阶乘、累加、幂次的题,十几项就能顶穿这个上限。判断方法很简单:粗算一下最大值,超过 2×10⁹ 就换long long,超过 1.8×10¹⁹ 就说明这题根本不是让你用内置类型做的。
再说高精度。这类题的信号词是「位数不超过」「很大的整数」「不用考虑前导零」。看到这些,直接从字符串入手,按位模拟竖式。核心逻辑只有三行:对应位相加、加上低位进位、取模留位、整除得新进位。
求平均值、比值的题要注意整数除法的截断方向。C++ 的整数除法是向零取整,负数时和「向下取整」不一样。如果题目要求向下取整而数据可能是负数,得单独处理。
3.2 字符串处理:下标、分类、整行读入
字符串题在号段里主要考四件事:统计某类字符个数、大小写转换、判断回文、按分隔符拆词。它们的共同前提是「把一整行完整读进来」。
cin >> s遇到空格就停,读不了带空格的句子。要用getline(cin, line)。但这里有个经典陷阱:如果前面用过cin >>,输入缓冲区里残留的换行符会被getline立刻吃掉,导致读到一个空串。
int n; cin >> n; cin.ignore(); // 丢掉 n 后面那个换行,否则下面的 getline 读到空行 string line; getline(cin, line);字符分类不要手写if (c >= 'a' && c <= 'z'),标准库有现成的:isalpha判字母、isdigit判数字、isspace判空白、toupper/tolower转大小写。但这类函数接受的是int,直接传char时如果字符值超过 127(中文、扩展 ASCII),会变成负数,行为未定义。规范写法是先转成unsigned char:isalpha((unsigned char)c)。这条不算玄学,是血泪经验,某些平台上不加转换就会随机出错。
判断回文用双指针从两端往中间收,遇到不相等立刻返回 false;注意题目是否要求「忽略大小写和非字母字符」,这是两个不同的难度。
3.3 排序与结构体:比较函数必须写成严格弱序
结构体排序是号段后段的常客,通常要求多关键字:先按分数从高到低,同分按名字字典序,再同按输入顺序。写法是给sort传一个自定义比较函数或者 lambda。
这里唯一需要记住的规则:比较函数必须满足严格弱序,也就是对任意两个元素x、y,cmp(x, y)和cmp(y, x)不能同时为真。实践中的表现就是——判等的时候用!=,不要用<=。
sort(v.begin(), v.end(), [](const Student& x, const Student& y) { if (x.score != y.score) return x.score > y.score; // 分数不同,降序 if (x.name != y.name) return x.name < y.name; // 同分看名字,升序 return x.id < x.id; // 再同看输入顺序 });如果写成if (x.score <= y.score) return true;,当两个元素分数相同时,cmp(a,b)和cmp(b,a)都返回真,sort的内部实现可能越界访问,表现为「本地跑得好好的,提交就 RE」。这个错误极难通过看代码发现,因为逻辑看起来是对的。
另一个细节是「稳定」。sort不保证相等元素的相对顺序,需要稳定时用stable_sort。但在这种题里,更省事的办法是给结构体加一个记录输入序号的字段,用它兜底,效果和稳定性一致,还不依赖库实现。
3.4 枚举与模拟:日期、进制、图形打印三件套
模拟题没有统一模板,但号段里反复出现的就三种。
日期题考的是闰年判断和月份天数表。闰年规则:能被 4 整除且不能被 100 整除,或者能被 400 整除。月份天数表不能写死在循环外,因为每一组数据的年份都不同:
bool isLeap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); } int month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 关键:month[2] 必须跟着当前年份走,多组数据时每次都要重算 month[2] = isLeap(y) ? 29 : 28;进制转换考的是逐位累乘。转十进制时不要用pow逆序加权,浮点误差会在高位上放大;正确写法是从左到右v = v * r + d,全程整数运算。十进制转其他进制用取模倒排,注意 0 要单独处理,否则输出空串。
图形打印考的是找规律。给一个菱形模板,核心是把「空格数」和「星号数」写成行号的函数,然后分上下两半各写一个循环:
// 上半部分含中间行:第 i 行(从 1 开始)空格 n-i 个,星号 2i-1 个 for (int i = 1; i <= n; ++i) { for (int j = 0; j < n - i; ++j) cout << ' '; for (int j = 0; j < 2 * i - 1; ++j) cout << '*'; cout << '\n'; }下半部分把i从 1 走到 n-1,空格数改成i,星号数改成2*(n-i)-1。写这类题先在纸上把前三行画出来,标上空格数和星号数,规律自然就出来了,硬凑公式反而慢。
4. 真题演练:五道典型题的逐行思路与代码
4.1 多组求和:先把读入方式定下来,再想逻辑
题目形态:连续输入若干组数据,每组先给一个整数 n,后面跟 n 个整数,求这组的和;读到输入结束为止。
思路顺序是固定的:先定终止条件(读到文件末尾),再定每组内部的读入循环,最后定输出格式(每组一行)。很多人一上来就写累加,结果外层循环条件写错,样例只过第一组。
#include <cstdio> int main() { int n; // 外层:只要能读到一个 n,就说明还有一组数据 while (scanf("%d", &n) == 1) { long long sum = 0; // 必须写在循环内部,每组清零 for (int i = 0; i < n; ++i) { int x; scanf("%d", &x); sum += x; // 用 long long 兜住 n 很大时的累加溢出 } printf("%lld\n", sum); // %lld 对应 long long,用 %d 会输出错值 } return 0; }讲解两句。scanf("%d", &n) == 1这个判断比!= EOF更严格,它挡住了「读到半个数字」的情况。sum用long long,是因为题目没说 n 和元素的范围,一万个十万相加就到 10⁹,五万个就顶穿了 int。输出格式串必须和类型匹配,long long用%lld,用%d在 64 位环境下会读到错误的字节。
4.2 大数相加:字符串模拟竖式
题目形态:输入两个很长的非负整数(位数可能上百),输出它们的和。
判分点只有一个:能不能不用内置类型把它算出来。做法是把两个数当字符串,从末尾往前逐位相加,维护一个进位变量。循环终止条件是「两个串都走完了,并且进位也是 0」——最后这个条件最容易漏,导致最高位的进位被丢掉。
#include <iostream> #include <string> #include <algorithm> using namespace std; int main() { string a, b; while (cin >> a >> b) { string res; int i = a.size() - 1, j = b.size() - 1, carry = 0; // 三个条件缺一不可:a 没走完、b 没走完、还有进位 while (i >= 0 || j >= 0 || carry) { int sum = carry; if (i >= 0) sum += a[i--] - '0'; // 字符减 '0' 得到数值 if (j >= 0) sum += b[j--] - '0'; res.push_back(char('0' + sum % 10)); // 留当前位 carry = sum / 10; // 进位带到下一轮 } reverse(res.begin(), res.end()); // 刚才是个位在前,要翻回来 // 结果可能是 "000",去掉前导零,但至少保留一位 size_t pos = res.find_first_not_of('0'); cout << (pos == string::npos ? "0" : res.substr(pos)) << endl; } return 0; }参数说明几个点。a[i--] - '0'里同时完成了「取值」和「下标前移」两件事,写紧凑但可读,不习惯就拆成两行。char('0' + sum % 10)是把数字位还原成字符,不加char转换在部分编译器上会报 narrowing 警告。去前导零那一段是必需的,因为输入可能是0和0,或者两数相加恰好是整十整百。
4.3 字符串统计:分类计数与整行读入
题目形态:输入一行字符,分别统计字母、数字、空格和其他字符的个数,输出四个数。
这题不难,但有两个点是成规模的失分源。第一,cin >>读不到空格,必须getline;第二,字符分类函数要传unsigned char。
#include <iostream> #include <string> #include <cctype> using namespace std; int main() { string line; int letters = 0, digits = 0, spaces = 0, others = 0; while (getline(cin, line)) { // 一行一行读,能处理多行输入 for (char c : line) { unsigned char u = (unsigned char)c; // 先转无符号,避免负值传入 if (isalpha(u)) letters++; else if (isdigit(u)) digits++; else if (c == ' ') spaces++; else others++; } } cout << letters << " " << digits << " " << spaces << " " << others << endl; return 0; }while (getline(...))的好处是自动处理「有多行」和「只有一行」两种情况,不用额外判断。判断空格用的是c == ' '而不是isspace(u),因为题目里的「其他字符」通常不含制表符,用isspace会把制表符也算进空格,和题意不符。这类细节必须回读题面确认,不能凭感觉。
4.4 结构体多关键字排序:比较函数是唯一的雷区
题目形态:给 n 个学生的姓名和成绩,按成绩从高到低输出;成绩相同按姓名升序;都一样按输入顺序。
写这题的正确姿势是先把结构体定出来,把「输入序号」当第三个字段存进去,然后写比较函数。比较函数按优先级逐层判断,每一层都用「不等」作为进入条件。
#include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; struct Student { string name; int score; int id; // 输入顺序,兜底用 }; int main() { int n; cin >> n; vector<Student> v(n); for (int i = 0; i < n; ++i) { cin >> v[i].name >> v[i].score; v[i].id = i; } sort(v.begin(), v.end(), [](const Student& x, const Student& y) { if (x.score != y.score) return x.score > y.score; // 第一关键字:分数降序 if (x.name != y.name) return x.name < y.name; // 第二关键字:姓名升序 return x.id < y.id; // 第三关键字:输入顺序 }); for (const auto& s : v) cout << s.name << " " << s.score << "\n"; return 0; }三个关键字,每一层都写!=再返回比较结果,这样任意两个元素之间只有一种确定关系,满足严格弱序。假如把第一行写成if (x.score <= y.score) return true;,分数相同时两个方向都返回真,sort的行为就变成未定义。这个 bug 在小数据上不显形,在几千条数据上直接崩,属于典型的环境相关错误。
输出用"\n"而不是endl,是因为endl每次都强制刷新缓冲区,数据量大时明显拖慢。题号段的数据量本来无所谓,但这是个值得从第一题就养成的手感。
4.5 日期与进制:状态每次重算
题目形态一:给年月日,输出这一天是本年的第几天。形态二:给一个数和进制,输出它在十进制下的值。
日期题的核心不是循环,是「月份天数表要跟着年份变」。写在循环外的month[2] = 28会让所有闰年数据全错,而且错误只出现在 2 月之后。
#include <iostream> using namespace std; bool isLeap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); } int main() { int month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int y, m, d; while (cin >> y >> m >> d) { month[2] = isLeap(y) ? 29 : 28; // 每次读入新日期都要重算,别提到循环外 int days = 0; for (int i = 1; i < m; ++i) days += month[i]; // 累加前面整月 days += d; // 加上当月天数 cout << days << endl; } return 0; }进制题的写法在 3.4 已经给过,这里强调一个判断顺序:先确认输入里有没有可能出现小写字母(a~f表示 10~15),有的话转换前先toupper。再确认进制范围,超过 16 的进制不能只靠单个字符表示一位,题目一般会避开,但读题时要看到这句。
5. 易错点:五个最冤的失分现场与纠正手法
5.1 输入输出类失分:格式错和读不全
错在哪:输出里多打了一句「请输入」。这是刚上手的人最常见的错,本地跑起来看着很友好,提交直接 WA。判题机做的是逐字符比对,你输出的每一个字符都会被算进答案。
为什么错:把控制台程序的习惯带进了判题环境。在线评测只认标准输出里的答案部分,任何提示语、单位说明、多余标点都算错。
怎么答对:提交前把printf和cout从头到尾扫一遍,只保留题目要求输出的内容。养成一个习惯:本地测试时把输入输出重定向到文件,对着文件看结果,比在终端里看更容易发现多余字符。
错在哪:多组数据的输出之间空行处理反了。题目要求「每组之间空一行」,很多人写成每组末尾加空行,最后一组后面就多出一个空行。
为什么错:「组间」和「组后」在只有一组数据时表现一样,两组以上才分得出来,而样例往往只给一组。
怎么答对:用「第一组特殊处理」的写法:先输出第一组,之后每一组都在输出结果前先打一个换行。这样无论多少组,末尾都不会多空行。
5.2 数据类型与边界类失分:溢出和越界
错在哪:累加用 int,结果在测试点 7 变成负数。题号段里求阶乘、求幂和、求累加的题,n 给到二三十就能顶穿 32 位。
为什么错:样例给的数据小,本地算出来是对的,误以为类型没问题。判题机的测试点专门卡边界。
怎么答对:读完题先粗算最大值再定类型:超过 2×10⁹ 用long long,超过 1.8×10¹⁹ 就说明要用高精度。输出格式串同步改成%lld。
错在哪:数组开得刚好等于题目上限。题目说「n 不超过 100」,就开int a[100],然后循环写到a[100]。
为什么错:下标从 0 开始,n 个数占的是 0 到 n-1,但只要循环条件写成i <= n,就会多写一位。这个越界在本地可能不崩,只是把相邻变量改了。
怎么答对:数组统一按上限加 5 到 10 开,循环条件统一写成i < n。写完之后专门检查一遍所有<=出现的位置。
5.3 逻辑顺序与初始化类失分:清零和重算
错在哪:多组数据的累加器定义在循环外面。第一组结果对,第二组开始翻倍。
为什么错:变量的作用域和生命周期没对齐。定义在外面的变量只初始化一次,后面每组都在上一组的结果上继续加。
怎么答对:把所有「每组都要重新开始」的变量——累加器、计数器、标志位、临时数组——统统定义在循环体内部第一行。这条规则不需要理解,照做就行。
错在哪:排序比较函数里用<=判等。小数据能过,数据一多就运行时错误。
为什么错:比较函数不满足严格弱序,sort内部的划分逻辑会越界。这个错误隐蔽性极强,因为代码看上去完全符合直觉。
怎么答对:比较函数里所有判等一律用!=,最后一行再返回兜底比较。写完拿一组「全部元素相同」的数据自测,能过基本就没问题。
注意:这五条失分里有四条和数据规模无关,用样例根本测不出来。自测时务必自己造边界数据,别只跑题目给的样例。
6. 答题技巧:机试现场的 40 分钟分配与提交自检清单
进了考场,题号段练出来的东西要以另一种方式用出来。基础题的正确策略不是「做出来」,而是「快且一次过」。一道 A+B 型题花十五分钟调试,后面就没时间了。
我自己的分配是这样的:读题 3 分钟,其中至少一半时间用来确认输入格式和输出格式,把「几组数据、怎么终止、要不要空行、保留几位小数」这四个问题在纸上写清楚。写代码 10 分钟,写完立刻用样例跑一遍。剩下时间造两组边界数据:一组最小值或空输入,一组刚好等于题目上限。全部通过再提交。
| 提交前检查项 | 具体看什么 | 出问题的典型表现 |
|---|---|---|
| 输入方式 | 单组、多组、先给组数、读到标志结束 | 样例过、提交只过第一组 |
| 终止条件 | EOF、== 1、!= 0 | 死循环超时,或漏最后一组 |
| 变量类型 | 累加和、乘积、中间结果 | 大测试点是负数 |
| 数组大小 | 是否按上限加余量,循环是否<而非<= | 本地正常,提交运行错误 |
| 初始化位置 | 累加器是否在循环体内清零 | 第二组开始结果翻倍 |
| 输出格式 | 行末空格、组间空行、小数位数、类型匹配 | 答案数值对,仍然 WA |
| 比较函数 | 判等是否用!=,是否满足严格弱序 | 小数据过,大数据崩溃 |
这张表我后来打印出来贴在显示器边上,每次提交前从上往下扫一遍,扫完再点提交。刚开始觉得繁琐,两三天之后就变成条件反射了。
还有一个押题方向的判断:如果题库里出现了「多组输入 + 结构体排序 + 日期或进制」的组合题,几乎可以肯定后面还有一道同类型的加强版。这类题在号段里是成组出现的,做完一道就把模板代码留着,下一道改两行就能用。真正拉开差距的不是会不会做,是能不能在十分钟内把模板改对。
从那以后我每次上机,不管题目多简单,都强制走一遍这张清单——先确认读入,再确认类型,最后确认输出。省下来的调试时间,远比检查花掉的多。希望帮到你。
本文还有配套的精品资源,点击获取