☰
1000-1099题号段AC代码包:判题机制与输入输出避坑
2026/10/31 8:38:28 网站建设 项目流程

简介:杭州电子科技大学在线OJ 1000~1099题解代码包,面向正在刷题、准备竞赛或系统学习C/C++算法的编程学习者,集中提供该题号区间内题目的可运行实现,帮助对照题目理解算法思路并完成查漏补缺。编号跨度从1000到1099,难度梯度清晰,便于按顺序练习或按专题检索。压缩包共90个文件,以62个cpp和16个c源码为主体,另含少量VC工程与调试产物,整体约1.1MB,便于在本地直接编译、调试和复现。内容覆盖基础算法、数据结构、数学应用、逻辑推理以及排序查找、动态规划、图论等常见题型,代码均已调试通过,可用来观察时间与空间复杂度的优化取舍,并学习调试测试、边界处理和工程组织经验。已有1840人学习下载,适合作为杭电OJ入门到进阶阶段的题解参考与思路拓展材料。

1. 一份 1000-1099 题号段的 AC 代码包:真正值钱的地方不是答案

多数在线评测平台的入门区就压在 1000-1099 这个题号段上:前面十几道是 A+B 这类输入输出热身,中间逐步上模拟、进制转换、日期推算、简单枚举,后段开始掺字符串处理和基础数论。这份资源把这 100 道题的通过代码按题号整理成一份能直接编译运行的合集,主体是 C 与 C++,少量题目带 Java、Python 版本。它的价值不在“抄答案”——这些题自己啃半小时多半也能过——而在于给你一份可对照的基准实现:同一道题别人怎么处理多组输入、怎么写 Case 编号、怎么避开行末空格,这些细节才是新手反复 WA 的真正原因。刚上手 OJ 的人、要带新人的助教、想快速把基础语法过一遍的老手,都能从里面挑出自己需要的那部分。

2. 题号段结构与判题机制:先搞清楚代码是跑在谁的地盘上

拿到一个按题号平铺的代码包,最容易犯的错是把它当成“本地能跑就算过”。在线评测的判题机和你的开发机是两套环境,编译参数、输入来源、输出比对方式全部不同。这一章先把这 100 题的分布摸清楚,再把判题流程拆开,最后落到“怎么在自己的机器上把这 100 个文件批量跑起来”。

2.1 1000-1099 的题型分布与代码组织方式

这个题号段的题目难度曲线相当平缓,但题型切换很快,常见分布大致是下面这样。注意具体题目内容各平台有差异,这里给的是这一类入门题号段的典型形态,看代码包时按这个框架去归类就行。

题号区间典型题型主要考察点高频翻车点
1000-1010输入输出热身、A+B 变体scanf/printf、多组数据EOF 判断写错导致死循环
1011-1030简单数学、进制转换、日期取模、循环、边界枚举int 溢出、闰年判断漏条件
1031-1055模拟、递推、简单排序数组操作、状态转移数组开小、下标越界
1056-1080字符串处理、字符统计读整行、大小写、分隔换行符残留、gets 已废弃
1081-1099简单数论、枚举优化素数筛、gcd、暴力剪枝时间复杂度估算错误

代码包通常按1000.c、1001.cpp这种“题号即文件名”的方式平铺,好一点的会分src/、input/、output/三个目录。我一般先把所有文件扫一遍,统计后缀名和行数,判断这份包是不是“同一批人写的”——风格统一的包,参考价值远高于东拼西凑的。下面这段脚本干的就是这件事:

#!/usr/bin/env bash # 统计代码包的文件构成:后缀分布、行数区间、疑似空文件 set -u root="${1:-.}" echo "== 后缀分布 ==" find "$root" -type f \( -name '*.c' -o -name '*.cpp' -o -name '*.java' -o -name '*.py' \) \ | sed 's/.*\.//' | sort | uniq -c | sort -rn echo "== 行数分布 ==" find "$root" -type f \( -name '*.c' -o -name '*.cpp' \) -print0 \ | xargs -0 wc -l 2>/dev/null \ | awk 'NF==2 && $2!="total" {n[$1]++} END {for (k in n) print k" 行: "n[k]" 个"}' echo "== 空文件与超短文件 ==" find "$root" -type f \( -name '*.c' -o -name '*.cpp' \) -size -100c -print

第一段用sed抠出后缀做计数,判断这份包以 C 还是 C++ 为主;第二段借wc -l做行数直方图,入门题的 AC 代码普遍在 15-60 行,如果某道题动辄两三百行,多半是写复杂了或者夹带了调试代码;第三段揪出 100 字节以下的文件,这类通常是占位符或者只写了int main(){}的空壳,别当成有效参考。xargs -0配合find -print0是为了扛住文件名里的空格,虽然题号命名一般不会有空格,但养成习惯不吃亏。

2.2 判题机到底怎么跑你的代码

在线评测的执行模型比多数人想的简单,也比多数人想的严格。提交之后,判题机做的事是:用平台预设的编译命令编译你的源码 → 起一个受限进程 → 把测试点数据从标准输入喂进去 → 收标准输出→ 和标准答案逐字节或逐 token 比对。

这里面有三个点直接决定你 WA 还是 AC。第一,输入只走 stdin,任何“请从文件读入”的写法在判题机上都会挂,因为测试数据文件名你根本不知道。第二,输出只认 stdout,printf到 stderr 的调试信息不影响判题,但会拖慢速度,而且很多平台会截断 stderr 导致你看不到自己的调试输出。第三,比对方式分两种:严格比对(逐字节,多一个空格就是 WA)和忽略行末空白的 token 比对(按空白切分后比较),后者宽松一些,但别赌,按严格比对写。

时间限制通常 1 秒,内存 32MB 到 256MB 不等。入门题号段的 100 道题里,绝大多数 O(n) 或 O(n²) 且 n 很小,正常写法不会超时;真正容易 TLE 的是两类:一是在循环条件里反复调用strlen,二是 C++ 里没解绑cin却读了几十万行。

提示:如果你本地跑得飞快、提交却 TLE,先把cin/cout换成scanf/printf试一次,这是排查方向里成本最低的一步。

2.3 把 100 个文件批量编译起来:目录规整与编译脚本

一份平铺的代码包,第一步是编译。逐题手敲g++是体力活,写个脚本一次编完,顺带把编译错误单独存下来。我一般会先建build/和logs/两个目录,源码原地不动,产物和日志分离,后面清理方便。

#!/usr/bin/env bash # 批量编译 1000-1099:C 用 gcc,C++ 用 g++,产物按题号命名 set -u src_dir="${1:-src}" out_dir="build" log_dir="logs" mkdir -p "$out_dir" "$log_dir" ok=0; bad=0 for src in "$src_dir"/*.c "$src_dir"/*.cpp; do [ -e "$src" ] || continue base=$(basename "$src") id="${base%%.*}" # 1000.c -> 1000 case "$src" in *.c) cc=gcc; std="-std=c11" ; lang=c ;; *.cpp) cc=g++; std="-std=c++17" ; lang=cpp ;; esac if "$cc" -O2 -Wall $std -o "$out_dir/$id" "$src" 2> "$log_dir/$id.$lang.err"; then ok=$((ok+1)) else bad=$((bad+1)) echo "编译失败: $src (详见 $log_dir/$id.$lang.err)" fi done echo "编译成功 $ok 个,失败 $bad 个"

这段脚本的关键在几处。-O2打开优化,是为了让本地跑出来的耗时接近判题机,不然你本地测 0.3 秒、提交 1.2 秒会很困惑。-Wall打开常用警告,入门代码里“变量未使用”“有符号与无符号比较”这类警告,往往就是隐藏 bug 的线索。-std=c11和-std=c++17显式指定标准,因为很多老平台的默认标准偏低,for (int i = 0; ...)在 C89 下直接编译不过,本地按高标准编过了,提交照样 CE。2> "$log_dir/$id.err"把错误重定向到文件,是为了 100 个文件一起编的时候屏幕不刷屏,出问题再回头翻。

注意:不要给批量编译加-Werror。入门题号段的代码里,未使用变量、隐式类型转换这类警告太常见,一加-Werror你会发现一半文件都编不过,反而掩盖了真正的问题。

编译完之后,挑几道有代表性题目手动跑一遍,输入用题目给的样例,输出贴回去比对。这一步别偷懒,我见过太多包里的代码“能编译但跑不对”,原因是作者提交时改过但没保存,或者压根是别人的错误代码被误收进来。

3. 输入处理:多组数据的三种写法与 EOF 陷阱

入门题号段里被问得最多的问题,不是算法,是“为什么我的程序提交后一直运行、最后 TLE”,或者“为什么只输出了一组”。根子都在输入循环上。这一章把 1000-1099 里出现的输入形态拆成三类,每类给可抄的写法。

3.1 scanf 返回值与 EOF 判断

scanf的返回值是成功赋值的变量个数,读到文件末尾或匹配失败时返回EOF(通常是 -1)。这三个信息量经常被混着用,而混用就是 bug 来源。

#include <stdio.h> int main(void) { int a, b; /* 写法一:判 == 2,最稳。只有两个整数都读到了才进入循环 */ while (scanf("%d %d", &a, &b) == 2) { printf("%d\n", a + b); } return 0; }
#include <stdio.h> int main(void) { int a, b; /* 写法二:先判断再使用。用 != EOF 时要注意, 若输入出现非数字字符,scanf 会一直返回 0 且不消耗该字符,形成死循环 */ while (scanf("%d %d", &a, &b) != EOF) { if (feof(stdin)) break; printf("%d\n", a + b); } return 0; }

两段代码的差别值得说清楚。写法一== 2是“读完两个才算一组”,如果某行只有一个数,循环直接退出,不会拿脏数据去算。写法二!= EOF是很多教材的默认写法,它在纯数字输入下没问题,但你只要手抖输入一个字母,scanf匹配失败返回 0,字母还留在缓冲区里,下一轮继续匹配失败,循环空转,判题机上就是标准的 TLE。所以这两个写法里我无条件推荐第一个。feof(stdin)那一行是为了在调试时提前跳出,正常提交可以去掉,因为!= EOF已经覆盖了文件结束的情况。

C++ 侧同理:

#include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); // 解绑 C 标准流,提速 cin.tie(nullptr); // 取消 cin 与 cout 的绑定 int a, b; while (cin >> a >> b) { // 流对象在 bool 上下文里表示读成功 cout << a + b << '\n'; // 用 '\n' 而不是 endl,避免每次刷新缓冲 } return 0; }

while (cin >> a >> b)能直接用,是因为operator>>返回流对象本身,流对象重载了explicit operator bool,读成功为true。sync_with_stdio(false)关掉 C 与 C++ 流的同步,cin.tie(nullptr)解除绑定,这两行是 C++ 选手的标配,能让cin的速度接近scanf。用'\n'代替endl也是同理——endl每次都会强制 flush,在输出量大的题上白送一堆系统调用。

3.2 字符串读入:三种方式各自的适用范围

1000-1099 后半段的字符串题,坑集中在“怎么读一整行”。三种主流方式对应三种场景,混用必翻车。

#include <iostream> #include <string> #include <cstdio> #include <cstring> using namespace std; int main() { /* 场景一:读一个不含空白的单词,scanf("%s") 或 cin >> s 都行 */ char word[128]; scanf("%127s", word); // 127 是宽度限制,防止缓冲区溢出 /* 场景二:读一整行(含空格),C 风格用 fgets,必须手动去换行 */ char line[512]; if (fgets(line, sizeof(line), stdin) != nullptr) { size_t n = strlen(line); if (n > 0 && line[n - 1] == '\n') line[n - 1] = '\0'; } /* 场景三:读一整行,C++ 风格用 getline,读的是 string,自动扩容 */ string s; getline(cin, s); return 0; }

第一段%127s的宽度限制是硬性要求,入门代码里写scanf("%s", buf)而buf只有 20 字节的,遇到长输入就是栈溢出,表现为莫名其妙的 Runtime Error。第二段fgets会把换行符一起读进去,所以在做字符串比较、求长度之前必须剥掉,否则strcmp(line, "YES")永远不等——这是入门题里出现频率最高的血泪经验之一。第三段getline会自动处理换行,但它有个前后顺序问题:如果前面用cin >> n读过整数,缓冲区里还留着一个换行符,紧接着的getline会读到一个空串。解决办法是在两者之间补一次cin.ignore()。

gets一定不要用。它在 C11 标准里已经被移除,很多平台的编译器直接报错,即使能编译也会因为不做边界检查而被判题机判定为不安全行为。老代码包里如果出现gets,改成fgets或者getline。

3.3 一组一输出 vs 全部缓存输出

输出时机看着是小事,但在这个题号段里,两种策略都有对应的题。判断依据很简单:题目要求“每行一个结果”的,边算边输出,代码短、内存省;题目要求“先输出 Case 数再输出全部结果”或者结果之间有严格空行分隔的,就得缓存。

#include <iostream> #include <vector> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); vector<string> out; // 先攒起来,最后一次性写出 out.reserve(1024); // 预留容量,避免反复扩容 int n; while (cin >> n) { int sum = 0, x; for (int i = 0; i < n; ++i) { cin >> x; sum += x; } out.push_back(to_string(sum)); } for (size_t i = 0; i < out.size(); ++i) { if (i) cout << '\n'; // 分隔符放在“前一个之后”,避开行末多余空行 cout << out[i]; } return 0; }

这段的写法有两个细节。reserve(1024)是预分配,避免vector在 push 过程中反复搬内存,对 100 道题这个量级其实无所谓,但养成习惯没坏处。分隔符的处理用“先判断下标再输出”,也就是把空行放在元素之间,而不是每个元素后面都跟一个——如果写成cout << out[i] << '\n';每条都加换行,最后会多出一个空行,严格比对的平台直接判 PE。这个套路在任何“输出 N 行、行间空行”的题上都通用,比事后pop_back干净得多。

4. 输出格式与数值精度:PE 比 WA 更让人抓狂

WA 至少说明你思路错了,PE 说明你思路对了但格式差一个字符。这个题号段里 PE 的占比不低,因为入门题特别爱考“Case #x: ”这种前缀和“两个数之间一个空格、行末不能有空格”的规则。这一章把格式和数值两类问题一次说透。

4.1 行末空格、空行与 Case 编号

行末空格是 PE 的头号来源。在 C 里,大家习惯写循环for (i = 0; i < n; i++) printf("%d ", a[i]);然后换行,这样每个元素后面都有一个空格,最后一个元素后面也有。判题机如果做严格比对,这就是错的。

#include <stdio.h> int main(void) { int t, n, a[105]; scanf("%d", &t); for (int cas = 1; cas <= t; ++cas) { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", &a[i]); printf("Case #%d:", cas); // 前缀单独输出,行末不带多余空格 for (int i = 0; i < n; ++i) { printf(i ? " %d" : "%d", a[i]); // 分隔符放在元素前面 } printf("\n"); } return 0; }

核心就一句:分隔符放在元素前面,第一个元素特殊处理。这样最后一行末尾天然没有空格。三元表达式i ? " %d" : "%d"是 C 里最省事的写法,比在循环里加if (i != n - 1)判断要短。

Case 编号的格式也是高频失分点。Case #1:和Case #1:和Case 1:在判题机眼里是三个不同的东西,冒号后面有没有空格、#有没有、大小写,必须一个字符一个字符对着题面抄。我见过因为Case写成case提交七次的人,血泪经验:拿到题先把输出模板复制到代码里当注释,别凭记忆敲。

空行的问题要注意:如果题目要求“每组数据之间输出一个空行”,正确写法是在第一组之后、每组之前打空行,而不是每组结束后打一个。前者最后不会多空行,后者会。

4.2 浮点数:printf 的舍入不是你想的那样

浮点题在这个题号段里通常是“保留两位小数”或者“求平均数”。看似简单,但printf("%.2f", x)的舍入行为和很多人以为的“四舍五入”不一样——它用的是当前的舍入模式,默认是 round-half-to-even(银行家舍入),也就是2.675可能输出2.67而不是2.68,因为它内部是二进制表示,2.675存进去其实是2.67499999...。

#include <cstdio> #include <cmath> int main() { double sum = 0.0; int n, x; scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", &x); sum += x; } double avg = sum / n; /* 手工加 eps 再做舍入,规避二进制表示带来的边界误差 */ double fixed = floor(avg * 100.0 + 0.5) / 100.0; printf("%.2f\n", fixed); return 0; }

floor(avg * 100.0 + 0.5) / 100.0是“先放大 100 倍、加 0.5、向下取整、再缩回去”的经典手工舍入,对正数有效,负数要改成减 0.5 或者用round。这个方法不是万能的,但在入门题的数值范围内足够稳。另一个更省事的做法是直接printf("%.2f", avg + 1e-9),加一个极小量把2.6749999推到2.675以上。两种都能过,手工那版更可控。

注意:不要用float做大数累加。float只有 24 位有效位,100 万个 1.0 加起来误差能到几十。这个题号段不考,但养成用double的习惯,往后走少踩坑。

4.3 溢出与类型:int 装不下的那些数

第四类高频错误是溢出。入门题的输入范围通常写明“n ≤ 10000”,但求和结果可能到 10^8,接近int上限(约 2.1×10^9);如果是“求 n!”或者“两个 10^9 相乘”,int必炸。

#include <cstdio> int main() { int n; while (scanf("%d", &n) == 1 && n > 0) { long long fact = 1; // 阶乘用 long long,int 到 13! 就溢出 for (int i = 2; i <= n; ++i) { if (fact > 1000000000000000000LL / i) { // 溢出预判,防止静默回绕 fact = -1; break; } fact *= i; } if (fact < 0) printf("overflow\n"); else printf("%lld\n", fact); } return 0; }

这段有两个知识点。一是long long是 64 位,能装到约 9.2×10^18,20!就超了,所以上面加了溢出预判:在乘之前先判断fact > LIMIT / i,等价于判断fact * i > LIMIT但不会溢出。二是printf输出long long必须用%lld,用%d会读到低 32 位,输出一个完全不相干的小数字——这个 bug 极其隐蔽,因为程序不报错,只是答案离谱。

另一个类型坑是unsigned与int混用。for (unsigned i = n; i >= 0; --i)这种写法是死循环,因为unsigned减到 0 再减就回绕成最大值。入门题里做倒序输出时很容易踩到,直接改用int就完事。

5. 避坑与排查:本地 AC 提交 WA 的五种典型情况

这一章按“现象 → 原因 → 解决”来写,都是这个题号段里反复出现的,顺序按出现频率从高到低排。

5.1 多组数据下全局变量和数组没清空

现象:单组测试自己跑对,提交后从第二组数据开始答案就乱七八糟,或者前几组对、中间某组开始错。

原因:把累加变量、计数数组、标记数组定义成了全局变量或者static变量。这类变量生命周期是整个程序,第一组跑完后残留着上一组的值,第二组直接在上面继续累加。

解决:每轮循环开头显式清零。数组用memset(arr, 0, sizeof(arr)),标量直接赋 0。更彻底的办法是把变量定义在while循环体内,让作用域天然隔离。我一般会在每组数据处理的入口处写一行注释// reset state,提醒自己这里必须清,因为这类 bug 光看代码很难发现——它“看起来”没问题。

5.2 行末空格与多余空行导致的 PE

现象:判题结果不是 WA 而是 PE(Presentation Error),或者在你自己的机器上肉眼比对完全一致。

原因:循环输出时每个元素后面都跟了分隔符,导致行末多一个空格;或者“每组之间空一行”写成了“每组之后空一行”,末尾多一个空行。

解决:分隔符改成前置,第一个元素特殊处理;空行改成判断“不是第一组”时再输出。输出前可以把结果重定向到文件,用cat -A看行末是不是有$前导的空格,比肉眼靠谱。

5.3 数组开小与越界访问

现象:本地小数据跑得好好的,提交大测试点直接 Runtime Error,有时甚至输出正确但被判 RE。

原因:题目给的 n 上限是 10000,代码里开了a[10000],但循环写的是i <= n,访问到a[10000]越界;或者题目有多个数组,只按经验开了 1000。

解决:数组大小按“上限 + 5”开,永远多留几个位置。写完代码把题目里的数据范围读一遍,在数组定义旁边写注释标上限。另外注意二维数组的乘积:int a[1005][1005]是 4MB,如果题目内存限制 32MB,开两三个就危险了。

5.4 时间复杂度的直觉误判

现象:逻辑正确、答案正确,但一直 TLE。

原因:两层循环里套了strlen或者string拼接,把 O(n) 的操作放大成 O(n²);或者 C++ 的cin没解绑,读 10 万行慢十倍。

解决:先把strlen的结果存到变量里,循环里不再重复调用。cin前面加ios::sync_with_stdio(false); cin.tie(nullptr);。还不行就换scanf重写输入部分,通常立刻见效。判断方法很简单:把输入输出全部换成 C 风格再交一次,能过就是 I/O 的锅,不能过才是算法问题。

5.5 编译器标准差异导致的编译错误

现象:本地g++编译通过,提交返回 Compile Error,报错指向for (int i = 0; ...)或者<bits/stdc++.h>找不到。

原因:不同平台的编译器版本和默认标准不同。老的判题机默认 C89,不允许在for里声明变量;部分平台不提供 GCC 的万能头文件。

解决:C 代码里把变量声明提到块的开头;C++ 代码老老实实写#include <iostream>、#include <cstdio>这些具体头文件,别图省事用万能头。本地编译时显式带-std=c11或-std=c++11,能提前暴露一部分标准问题。

6. 用对拍把 100 道题做一次回归测试

100 道题逐个手测太慢,但全不信又不行。我的做法是给代码包配一套轻量对拍:对每道题写一个随机输入生成器和一个暴力参考解,跑几百组小数据比对输出。这套东西搭一次,之后改代码随时能重跑。

生成器用 Python 写最省事,一个通用模板覆盖大部分“读 n 个数”的题:

# gen.py:随机生成一组输入,参数从命令行传入,方便不同题复用 import random, sys n = random.randint(1, 20) # 小数据更容易暴露边界 lo, hi = -100, 100 print(n) print(' '.join(str(random.randint(lo, hi)) for _ in range(n)))
# check.py:对拍主循环,比对两个可执行文件的输出 import subprocess, sys, filecmp fast, slow = sys.argv[1], sys.argv[2] rounds = int(sys.argv[3]) if len(sys.argv) > 3 else 300 for i in range(rounds): with open('in.txt', 'w') as f: subprocess.run(['python3', 'gen.py'], stdout=f, check=True) a = subprocess.run([fast], stdin=open('in.txt'), capture_output=True, text=True).stdout b = subprocess.run([slow], stdin=open('in.txt'), capture_output=True, text=True).stdout if a.split() != b.split(): # 按 token 比,忽略行末空白 print(f'第 {i} 轮不一致') print('输入:', open('in.txt').read()) print('待测:', a) print('参考:', b) break else: print(f'{rounds} 轮全部一致')

a.split() != b.split()这一行是按空白切分后比较,能自动忽略行末空格和空行差异——这正是对拍应该忽略的东西,格式问题靠肉眼看题面解决,不靠对拍。参考解不用写得多优雅,写个思路完全不同但一定正确的暴力版本就行,比如求最值的题用排序代替维护,求和的题用双重循环代替前缀和。两边思路独立,才有交叉验证的意义。

跑的时候有个技巧:先跑 50 轮小数据(n ≤ 20),专抓边界和清零问题;再跑 50 轮中数据(n ≤ 1000),抓溢出和数组越界。小数据能过、大数据挂的,八成是int溢出或者数组开小;小数据就挂的,多半是状态没清或者循环条件写反。

对拍阶段数据规模主要抓的问题建议轮数
边界轮n ≤ 5,含 0 和负数特殊输入、空集、单元素100
小数据轮n ≤ 20状态未清零、下标越界100
中数据轮n ≤ 1000溢出、效率、重复计算50

这套流程我最早是懒,觉得入门题没必要对拍,结果一道“多组数据求和”的题在全局数组上栽了两次,第二次还是同一个位置。从那以后我每次从代码包里拿现成实现用,都强制先过一遍清零检查和 50 轮小数据对拍,哪怕题目再简单。这个习惯帮我省下的时间,远超写对拍脚本的那二十分钟。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询