1000-1099题号段AC代码包:判题机制与输入输出避坑 简介杭州电子科技大学在线OJ 10001099题解代码包面向正在刷题、准备竞赛或系统学习C/C算法的编程学习者集中提供该题号区间内题目的可运行实现帮助对照题目理解算法思路并完成查漏补缺。编号跨度从1000到1099难度梯度清晰便于按顺序练习或按专题检索。压缩包共90个文件以62个cpp和16个c源码为主体另含少量VC工程与调试产物整体约1.1MB便于在本地直接编译、调试和复现。内容覆盖基础算法、数据结构、数学应用、逻辑推理以及排序查找、动态规划、图论等常见题型代码均已调试通过可用来观察时间与空间复杂度的优化取舍并学习调试测试、边界处理和工程组织经验。已有1840人学习下载适合作为杭电OJ入门到进阶阶段的题解参考与思路拓展材料。1. 一份 1000-1099 题号段的 AC 代码包真正值钱的地方不是答案多数在线评测平台的入门区就压在 1000-1099 这个题号段上前面十几道是 AB 这类输入输出热身中间逐步上模拟、进制转换、日期推算、简单枚举后段开始掺字符串处理和基础数论。这份资源把这 100 道题的通过代码按题号整理成一份能直接编译运行的合集主体是 C 与 C少量题目带 Java、Python 版本。它的价值不在“抄答案”——这些题自己啃半小时多半也能过——而在于给你一份可对照的基准实现同一道题别人怎么处理多组输入、怎么写 Case 编号、怎么避开行末空格这些细节才是新手反复 WA 的真正原因。刚上手 OJ 的人、要带新人的助教、想快速把基础语法过一遍的老手都能从里面挑出自己需要的那部分。2. 题号段结构与判题机制先搞清楚代码是跑在谁的地盘上拿到一个按题号平铺的代码包最容易犯的错是把它当成“本地能跑就算过”。在线评测的判题机和你的开发机是两套环境编译参数、输入来源、输出比对方式全部不同。这一章先把这 100 题的分布摸清楚再把判题流程拆开最后落到“怎么在自己的机器上把这 100 个文件批量跑起来”。2.1 1000-1099 的题型分布与代码组织方式这个题号段的题目难度曲线相当平缓但题型切换很快常见分布大致是下面这样。注意具体题目内容各平台有差异这里给的是这一类入门题号段的典型形态看代码包时按这个框架去归类就行。题号区间典型题型主要考察点高频翻车点1000-1010输入输出热身、AB 变体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 NF2 $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任何“请从文件读入”的写法在判题机上都会挂因为测试数据文件名你根本不知道。第二输出只认 stdoutprintf到 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-1099C 用 gccC 用 g产物按题号命名 set -u src_dir${1:-src} out_dirbuild log_dirlogs mkdir -p $out_dir $log_dir ok0; bad0 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) ccgcc; std-stdc11 ; langc ;; *.cpp) ccg; std-stdc17 ; langcpp ;; esac if $cc -O2 -Wall $std -o $out_dir/$id $src 2 $log_dir/$id.$lang.err; then ok$((ok1)) else bad$((bad1)) echo 编译失败: $src (详见 $log_dir/$id.$lang.err) fi done echo 编译成功 $ok 个失败 $bad 个这段脚本的关键在几处。-O2打开优化是为了让本地跑出来的耗时接近判题机不然你本地测 0.3 秒、提交 1.2 秒会很困惑。-Wall打开常用警告入门代码里“变量未使用”“有符号与无符号比较”这类警告往往就是隐藏 bug 的线索。-stdc11和-stdc17显式指定标准因为很多老平台的默认标准偏低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); vectorstring 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 longint 到 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^1820!就超了所以上面加了溢出预判在乘之前先判断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 而是 PEPresentation 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这些具体头文件别图省事用万能头。本地编译时显式带-stdc11或-stdc11能提前暴露一部分标准问题。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], stdoutf, checkTrue) a subprocess.run([fast], stdinopen(in.txt), capture_outputTrue, textTrue).stdout b subprocess.run([slow], stdinopen(in.txt), capture_outputTrue, textTrue).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 轮小数据对拍哪怕题目再简单。这个习惯帮我省下的时间远超写对拍脚本的那二十分钟。希望帮到你。本文还有配套的精品资源点击获取