ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

杭电OJ 2026-2035题解:C语言基本功与格式控制的经典训练

杭电OJ 2026-2035题解:C语言基本功与格式控制的经典训练 杭电OJ的题号从1000开始越往后难度越高但2026到2035这个区间非常特殊——它基本不带算法成分全是基础语法、字符串处理和格式控制。我当年从这一批题目开始养成了很多习惯读题先看输入输出格式、写循环前先想边界、看到多组数据就条件反射地写while(scanf(...)!EOF)。这十道题不是什么高深东西但如果你能一次性全AC至少在C语言的基本功上已经不再心虚。这篇文章就把2026~2035逐题拆开讲一讲每道题的核心思路、完整代码以及那些让你反复PEPresentation Error的格式坑。1. 为什么是2026~2035这个区间到底能练出什么1.1 十道题的分工与难度曲线2026到2035这十道题在杭电OJ的题目序列里属于“基础中的基础”。它们的核心考点可以分成三类字符串处理、数学计算、格式化输出。题号题目名称核心考点最容易踩的坑2026首字母变大写字符串遍历、字母大小写转换读取包含空格的整行输入2027统计元音字符统计、输出格式每组数据之间的空行处理2028Lowest Common Multiple Plus最小公倍数、gcd先除后乘防止溢出2029Palindromes easy version回文串判断输入带空格的字符串实际不含空格2030汉字统计字符编码、字节判断中文字符在GBK下占两个字节2031进制转换进制转换、字符数组逆序负数处理、大于10的字母输出2032杨辉三角二维数组递推行末空格、组间空行2033人见人爱AB时间进制模拟分秒满60进位后小时的范围2034人见人爱A-B集合差集、排序输出NULL、行末空格2035人见人爱A^B快速幂取模结果是否需要前导零从表格可以看出来这一批题目几乎没有“思维难度”但对编码习惯的要求极高。你不需要会什么算法只需要老老实实把每一行的输入读进来、把每一种情况都考虑到、把每一个空格和换行都处理对。这就是它们存在的价值把一个毛躁的新手磨成一个能在OJ上稳定提交的选手。1.2 适合什么水平的人刷如果你刚学完C语言的控制结构、数组和字符串函数搞不清gets和scanf(%s)的区别不知道什么叫“多组输入”那么这个区间就是给你准备的。我当时就是跟着老师讲完循环和数组之后第一次打开杭电OJ从2026开始一题一题往后交。前几题WA得很惨后来发现问题全出在换行符上——这种教训比看再多书都记得牢。如果你已经在刷水题这个区间也可以作为热身。每道题平均耗时十分钟正好用来找回手感。当然如果你是准备考研机试或者求职笔试的C语言基础题这十道题的题型也非常典型值得反复刷到闭着眼睛能写出来。1.3 刷题前的输入输出基本功提醒在进入具体的题之前先统一说一个杭电OJ的通用规则大部分题目都是多组输入而且不明确告诉你多少组。这时候必须用while (scanf(%d, n) ! EOF) { // ... }如果题目先给一个T表示数据组数则用int T; scanf(%d, T); while (T--) { // ... }还有一种是以特定值结束比如2034题n0 m0就是终止条件。写的时候要区分清楚否则要么死循环要么把终止行也当成正常数据处理。另外要注意scanf(%d)读完之后行尾的换行符还留在输入缓冲区里。如果你接下来要用gets()读一行字符串就得先用getchar()把那个换行吃掉。这个细节贯穿整个2026~2035区间几乎每道题都有这个影子。2. 字符串题里的编码与边界陷阱2026、2027、2029、20302.1 2026 首字母变大写小心换行符和空格题目要求很简单输入一行英文句子把每个单词的首字母变成大写其他字母保持不变。比如输入hello world输出Hello World。如果直接用scanf(%s, str)遇到空格就会停止根本读不到整行。所以必须使用能够读取包含空格的一行字符串的方法。在老杭电OJ上gets()直接用。虽然C11标准已经把它移除了但GCC编译器仍然兼容很多ACM题解里还是能看到。为了更规范也可以用fgets(str, sizeof(str), stdin)但要注意它会把末尾换行也读进来。处理逻辑是遍历字符串如果当前字符是字母并且它是第一个字符或者它前面的字符是空格那么它就是单词首字母把它转成大写。如果当前字符不是首字母是其他位置的小写字母不需要处理。注意题目没说单词之间的空格数量可能多个空格所以判断条件用“前一个字符是空格”就足够。#include stdio.h #include string.h #include ctype.h int main() { char s[1000]; while (gets(s)) { int len strlen(s); for (int i 0; i len; i) { if (islower(s[i])) { if (i 0 || s[i - 1] ) { s[i] - 32; } } } printf(%s\n, s); } return 0; }这里有个小经验判断首字母时i 0 || s[i-1] 这个条件的顺序不能反过来。如果写成s[i-1] || i 0在i等于0时访问s[-1]属于未定义行为可能在某些编译器上直接崩溃。虽然很多环境下数组越界的读取碰巧没事但这不是好习惯。2.2 2027 统计元音别把换行算进去2027题的输入格式是先给一个整数n表示后面有n行字符串每行字符串中可能包含空格。要求统计每行中a、e、i、o、u这五个元音字母出现的次数不区分大小写并把结果按格式输出。这里最大的坑是输入缓冲。如果写成scanf(%d, n); gets(s);第一次gets会直接读到scanf留下的换行符导致字符串为空。所以要在scanf之后加一行getchar()。输出格式要注意每组数据之间有一个空行最后一组后面没有多余空行。实现上可以加一个flag变量第一组不打印空行之后的每组打印前先输出空行。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); int first 1; while (n--) { char s[1000]; gets(s); int cnt[5] {0}; for (int i 0; s[i]; i) { char c s[i]; if (c A c Z) c 32; if (c a) cnt[0]; else if (c e) cnt[1]; else if (c i) cnt[2]; else if (c o) cnt[3]; else if (c u) cnt[4]; } if (!first) printf(\n); first 0; printf(a:%d\n, cnt[0]); printf(e:%d\n, cnt[1]); printf(i:%d\n, cnt[2]); printf(o:%d\n, cnt[3]); printf(u:%d\n, cnt[4]); } return 0; }我见过有人在这里用scanf(%c)逐个读字符然后判断换行。这样也能做但代码会啰嗦很多。直接用gets是最符合题意的。2.3 2029 回文判断用对结束标志2029题问的是一串字符是不是回文。输入的第一行是n之后是n个不含空格的字符串。判断方式就是双指针从两端往中间走任何一处不相等就说明不是回文。这题本身没有难度但要注意一个边界字符串长度可能为1循环条件用i j而不是i len/2否则会重复比较中间字符。还有在处理指针时用strlen(s)得到长度然后j len - 1别写成j len那样会把\0也算进去。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); while (n--) { char s[1000]; gets(s); int len strlen(s); int i 0, j len - 1; int ok 1; while (i j) { if (s[i] ! s[j]) { ok 0; break; } i; j--; } if (ok) printf(yes\n); else printf(no\n); } return 0; }有人看到“回文”就想把字符串逆序再比较那需要额外的数组和拷贝没有必要。双指针的做法既省空间又直观。2.4 2030 汉字统计GBK编码和字节数的坑2030题可能是这个区间里最值得思考的一道。题目要求统计一段文本中汉字的个数。看起来无从下手其实核心是理解汉字在计算机里的存储方式。在杭电OJ的环境下默认使用GBK/GB2312编码。一个汉字占用两个字节并且每个字节的最高位都是1。如果使用char类型读取这两个字节会被当成有符号的负数。所以判断标准可以写成如果某一个字符的ASCII码小于0就说明它属于一个汉字字符。统计到多少个这样的字节再除以2就是汉字个数。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); while (n--) { char s[1000]; gets(s); int cnt 0; for (int i 0; s[i]; i) { if (s[i] 0) cnt; } printf(%d\n, cnt / 2); } return 0; }注意这里不能直接把cnt当成汉字数输出因为每个汉字占两个字节。另外在有些环境下char可能默认是无符号的那就需要把s[i]强制转成unsigned char再判断是否大于127。不过杭电的GCC环境下直接写s[i] 0是可行的。我调试的时候试过printf(%d\n, (unsigned char)s[i])能看到汉字首字节的值是大于127的所以判断0和127本质是一样的。这道题也给你提了个醒在OJ上遇到统计中文的题目一定要先想清楚编码格式。如果是UTF-8每个汉字占三个字节判断方式又不一样。3. 数学题背后的模板gcd、进制、快速幂2028、2031、20353.1 2028 最小公倍数先除后乘防溢出2028题是最小公倍数的经典入门题。输入多组测试每组第一行是n接下来是n个正整数要求输出这n个数的最小公倍数。求多个数的最小公倍数核心是掌握两个数的最小公倍数公式lcm(a, b) a / gcd(a, b) * b为什么先除后乘因为a * b可能溢出尤其在两个数都接近int上限的时候。先除以最大公约数结果一定小于等于其中一个数再乘另一个数时也不会超过long long范围。所以稳妥的做法是用long long存中间结果。#include stdio.h int gcd(int a, int b) { while (b) { int t a % b; a b; b t; } return a; } int main() { int n; while (scanf(%d, n) ! EOF) { long long ans 1; for (int i 0; i n; i) { int x; scanf(%d, x); ans ans / gcd(ans, x) * x; } printf(%lld\n, ans); } return 0; }当n1时只有一个数字最小公倍数就是它本身。上面的代码里ans初始为1第一次循环时gcd(1,x)1得到ansx正确覆盖了这种情况。3.2 2031 进制转换负数处理和数组逆序2031题要求把十进制整数N转换成R进制数其中R在2到16之间。输入可能包含负数需要先输出负号再处理绝对值。输出时大于等于10的数字用大写字母A~F表示。思路很直接不断用N对R取余把余数转换成字符存进数组然后N / R直到N变成0。最后从数组末尾向前输出。#include stdio.h int main() { int n, r; while (scanf(%d%d, n, r) ! EOF) { if (n 0) { printf(-); n -n; } char res[100]; int len 0; if (n 0) { printf(0\n); continue; } while (n) { int t n % r; if (t 10) res[len] t 0; else res[len] t - 10 A; n / r; } for (int i len - 1; i 0; i--) { printf(%c, res[i]); } printf(\n); } return 0; }这里有一个很容易写错的地方把n -n放在while (n)循环之前。如果n是int最小值取负会溢出但本题的输入范围通常不会这么极端。更稳妥的办法是使用abs()函数然后存储到unsigned或long long中。3.3 2035 A^B最后三位快速幂取模的入门模板2035题看起来是算乘方但如果直接pow再取余数值一大就溢出。正确做法是边乘边取模利用模运算的性质(a * b) % m ((a % m) * (b % m)) % m求A^B mod 1000只要在乘法过程中每一步都取模1000结果就只保留最后三位。但如果你用循环乘B次遇到大B会超时所以需要用快速幂把复杂度降到O(log B)。#include stdio.h int main() { int a, b; while (scanf(%d%d, a, b) ! EOF) { if (a 0 b 0) break; int ans 1; int base a % 1000; while (b) { if (b 1) { ans (ans * base) % 1000; } base (base * base) % 1000; b 1; } printf(%d\n, ans); } return 0; }这个代码的退出条件是a0 b0这也是题目中给出的输入结束条件。关于结果是否需要补前导零题目说的是“最后三位数表示的整数”所以输出普通整数即可无需用%03d。我在这题上纠结过后来看样例输出才发现它不会补零。4. 图形输出与集合操作里的格式强迫症2032、2033、20344.1 2032 杨辉三角行尾空格是Presentation Error高发区2032题要求输出杨辉三角。输入多组数据每组一个n输出前n行杨辉三角。每一行数字之间用一个空格隔开行末不能有多余空格。每个杨辉三角之间用一个空行隔开最后一个三角后面不能有多余空行。杨辉三角的递推关系很明确第i行第j列的数等于上一行第j-1和第j列的和。用二维数组保存比较直观#include stdio.h int main() { int n; int first 1; while (scanf(%d, n) ! EOF) { if (!first) printf(\n); first 0; int a[30][30] {0}; for (int i 0; i n; i) { a[i][0] 1; a[i][i] 1; for (int j 1; j i; j) { a[i][j] a[i-1][j-1] a[i-1][j]; } } for (int i 0; i n; i) { for (int j 0; j i; j) { if (j) printf( ); printf(%d, a[i][j]); } printf(\n); } } return 0; }输出格式里最容易PE的就是行末空格。如果写成for (int j 0; j i; j) { printf(%d , a[i][j]); }画蛇添足的行尾空格会让你一直收到Presentation Error。这种错误在OJ上不算WA但同样不算AC而且你很难一眼看出来。建议所有输出空格的题都采用“先判断是否需要空格再输出”的思想。4.2 2033 时间加法进位模拟的思路2033题是“人见人爱AB”这里的AB不是普通整数相加而是两个时间相加。每组输入给出两个时间分别包含时、分、秒要求把两个时间相加秒和分满60进位。思路就是小学学过的进位制把秒位、分位、时位分开处理#include stdio.h int main() { int T; scanf(%d, T); while (T--) { int h1, m1, s1, h2, m2, s2; scanf(%d%d%d%d%d%d, h1, m1, s1, h2, m2, s2); int sec s1 s2; int minute m1 m2 sec / 60; sec % 60; int hour h1 h2 minute / 60; minute % 60; printf(%d:%d:%d\n, hour, minute, sec); } return 0; }有些同学会纠结小时要不要对24取模。题目没有要求所以不要自作聪明。输出总的小时数即可。这题的关键是理解“秒满60向分进位分满60向时进位”的顺序。4.3 2034 集合差集排序、去重与逐个输出2034题是集合A-B输出A中存在但B中不存在的元素并按从小到大排序。输入多组每组第一行是n和m当n0 m0时结束。接下来一行是A集合的n个元素再一行是B集合的m个元素。思路是先把A和B分别排序然后遍历A中的每个元素在B中查找是否存在。如果不存在就输出。由于集合元素范围不大可以用两层循环或者双指针。注意输出格式每个元素后面跟一个空格但最后一个元素后没有空格。如果没有剩余元素输出NULL。#include stdio.h #include stdlib.h int cmp(const void *a, const void *b) { return *(int *)a - *(int *)b; } int main() { int n, m; while (scanf(%d%d, n, m) ! EOF) { if (n 0 m 0) break; int a[100] {0}, b[100] {0}; for (int i 0; i n; i) scanf(%d, a[i]); for (int i 0; i m; i) scanf(%d, b[i]); qsort(a, n, sizeof(int), cmp); qsort(b, m, sizeof(int), cmp); int printed 0; for (int i 0; i n; i) { int found 0; for (int j 0; j m; j) { if (b[j] a[i]) { found 1; break; } } if (!found) { if (i 0 a[i] a[i-1]) continue; // 去重 if (printed) printf( ); printf(%d, a[i]); printed 1; } } if (!printed) printf(NULL); printf(\n); } return 0; }这里我在输出去重时用了一个简单的continue判断因为集合本身理论上没有重复元素但为了防止题目数据出现意外加上不会影响正确性。如果审题严谨也可以不写。另一个常见错误是输出最后一个元素后多加了空格导致PE上面的写法只有在printed为1时才输出空格完美避开了。5. 从这批题延伸出去的几个提醒5.1 多组输入while(scanf(...)!EOF)的统一写法刷完2026~2035你会发现百分之八十的题目都是“多组输入”。我建议把所有输入模板都固化成肌肉记忆写代码的时候看到题目描述里出现“输入数据有多组”就自动敲出EOF循环。如果是先给组数的就把while (T--)和读入字符串之间的换行处理写对。这两种模板熟到不用动脑后面刷更复杂的题才不会在这些基础细节上花时间。5.2 为什么我推荐用C而不是C或Python刷这种基础题不是说C不好而是在这种纯语法题上C语言有它的天然优势你对内存、字符编码、指针的感受更直观。比如2030题汉字统计用C能直接感知到字节的最高位换成Python反而因为封装太完善而不容易理解。另外杭电OJ的很多历史题解都是C/C你搜资料时不会因为语言不同而隔一层。等你把C的基础打牢再用C的STL去刷更高阶的题目路线会很顺畅。5.3 刷完这个区间之后下一步看什么2026~2035只是十道题但它帮你补上了“输入输出格式控制”和“边界条件”这两块短板。下一步可以顺着题号继续刷2036到2045里面有凸多边形面积、字符串查找、素数判断等经典题难度会缓慢上升。也可以开始接触C标准库里的排序和容器比如用sort替代qsort用map做更复杂的统计。但如果2032的杨辉三角你都能一遍写对格式那么你已经具备了继续往下走的资格。最后再说一个我自己的习惯刷完每一题后不管AC还是WA都会把代码存下来按题号命名。后面碰到类似的输出格式题直接翻以前的代码对照比重新调试省时间得多。这批2026到2035的代码成了我后来最常翻的笔记之一。
返回列表