ARTICLE DETAIL

资讯详情

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

杭电OJ 2026-2035题解:C语言入门到AC的十道经典题

杭电OJ 2026-2035题解:C语言入门到AC的十道经典题 “杭电oj”这四个字几乎是每个从零开始刷题的人绕不开的起点。我当年在2026到2035这个区间里反复刷整整花了一周多把字符串、进制、集合、快速幂这些基础概念从头捋了一遍。现在回头看这批题真正的价值不在于题号连在一起而在于它恰好覆盖了“语法看得懂”到“代码能AC”那一段最难受的距离。适合刚学完C语言基础、准备打ACM或者应对机试的人至于已经刷了几百题的老手也可以拿这些题当“热身清单”快速找回手感。说句实在话十道题里有六道都是“一眼能看穿思路但一写就错”的典型比如输出格式多一个空格、字符串读入时被空格卡住、汉字统计忘记除以2。这篇文章就把我当年踩过的坑和最终采用的写法完整拆开照着一题一题过基本都能稳稳AC。1. 这批题到底在考什么从2026到2035的整体拆解1.1 十道题全景速览在动手之前先把十道题铺开看一眼心里有个整体地图。2026到2035正好是一个以C语言核心语法为基准的题目段没有复杂的图论和高级数据结构全部聚焦在顺序结构、循环、数组、字符串、函数和简单数论上。题号题目主题核心考点上手难度2026首字母变大写字符串整行读取、单词边界判断低2027统计元音字母字符计数、输出格式控制低2028n个数的最大公倍数变体辗转相除法、防溢出中2029回文串判断双指针扫描、字符串对称比较低2030汉字统计汉字编码特性、字节级判断中2031进制转换短除法、倒序输出、字母映射中2032杨辉三角二维数组、递推关系中2033时间相加进位模拟、多组数据处理低2034人见人爱A-B集合差集、排序中2035人见人爱A^B快速幂、取模运算较高注意我特意把“难度”标了出来不是劝退而是让你分配时间时有数。2035的快速幂在入门阶段确实算一个小门槛但把它拆成二进制原理来看并没有想象中难。1.2 为什么说这是入门黄金区间很多新人喜欢一上来就啃DP、搜索结果连输入输出格式都搞不明白白白消耗热情。2026到2035这段题的好处在于难度是梯度上升的前几道题几乎就是在练“怎么把一句话翻译成代码”到中间开始牵扯数组和数学最后用快速幂给整个区间拔个高。更关键的是这十道题反复训练了几个OJ刷题逃不开的基本功如何正确读取带空格的字符串如何控制输出空行如何让程序在EOF下连续处理多组数据如何避免int溢出。这些能力不在这批题里练扎实后面做任何一套模拟题都会吃亏。我自己的经验是把这一区间当成一个“小周目”先全部AC一遍再找几道容易PE格式错误的题重写刻意练习输出细节。刷完后再去碰难度更高的题目心里会稳很多。2. 字符串和字符处理的四道题2.1 首字母变大写与回文判断2026、2029先说2026这道题看起来简单但第一个大坑就是读入。题目输入是一个英文句子里面带空格如果你下意识用scanf(%s)去读只能读进第一个单词后面的全被丢掉了。正确做法是整行读入C语言里用getsC用getline。我在代码里用的是gets(s)然后循环处理每一个字符。判断“单词首字母”的条件有两个要么是字符串的第一个字符要么前一个字符是空格。满足条件后再判断当前字符是不是小写字母如果是就减32转大写。这里有个容易忽略的点题目只要求把首字母变大写其余字符不能动所以不能整个字符串做大小写转换。#include stdio.h #include string.h int main() { char s[128]; while (gets(s)) { int len strlen(s); for (int i 0; i len; i) { if (i 0 || s[i - 1] ) { if (s[i] a s[i] z) { s[i] - 32; } } } puts(s); } return 0; }2029回文判断用的是双指针思路一头一尾往中间走发现不相等就立刻标记失败。需要注意的是判断范围只到中间位置也就是i j时比较这样无论是奇数长度还是偶数长度都不会漏。输出格式是yes和no全小写别写成Yes。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); while (n--) { char s[128]; gets(s); int i 0, j strlen(s) - 1, flag 1; while (i j) { if (s[i] ! s[j]) { flag 0; break; } i; j--; } puts(flag ? yes : no); } return 0; }这两道题放在一起刷的原因很实在都是对字符串逐字符操作读入方式、边界判断、输出细节全都一脉相承。把2026的“单词边界”和2029的“双指针”理解了字符串类基础题就算过了第一关。2.2 元音统计与汉字统计2027、20302027统计元音本身逻辑不难但又是输出格式的“经典坑”。题目要求按a:e:i:o:u:的形式输出每个元音出现次数并且每组数据之后要输出一个空行。很多人在这一步拿不到AC不是因为统计错而是因为空行位置不对。多组数据时我建议直接“先输出本次结果再输出一个空行”这样每组后面都有空行和样例保持一致。统计时我用的switch语句逐个字符判断。题目本身统计的是小写元音字母但如果字符串里出现了大写保险做法是大写小写都统计进去。这里还藏着一个常见的缓冲区问题先用scanf(%d, n)读入组数后缓冲区里会残留一个换行符必须用getchar()把它吃掉否则后面的gets读到的会是空串。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); while (n--) { char s[128]; gets(s); int a 0, e 0, i 0, o 0, u 0; for (int j 0; j strlen(s); j) { if (s[j] a || s[j] A) a; else if (s[j] e || s[j] E) e; else if (s[j] i || s[j] I) i; else if (s[j] o || s[j] O) o; else if (s[j] u || s[j] U) u; } printf(a:%d\ne:%d\ni:%d\no:%d\nu:%d\n\n, a, e, i, o, u); } return 0; }2030汉字统计就更有意思了。很多人第一次看到这题是懵的因为C语言里字符串存的是ASCII码汉字该怎么识别关键在编码层面一个汉字在GBK编码下占两个字节而且每个字节的最高位都是1。在signed char下最高位为1的字节会被解释成负数所以遍历字符串时只要判断s[i] 0就能数出汉字“字节”的数量。但这里有一个巨大的坑一个汉字占两个字节所以你统计出来的cnt是汉字个数的两倍必须输出cnt / 2。我第一次做这题直接把cnt交上去怎么都想不通为什么WA后来才意识到是忘了除以2。还有一个细节是必须整行读取因为汉字可能出现在句子的任何位置用scanf(%s)读中英文混合串很容易被空格截断。#include stdio.h #include string.h int main() { int n; scanf(%d, n); getchar(); while (n--) { char s[1024]; gets(s); int cnt 0; for (int i 0; i strlen(s); i) { if (s[i] 0) cnt; } printf(%d\n, cnt / 2); } return 0; }3. 数学运算的四道题3.1 最小公倍数与进制转换2028、20312028求n个数的最小公倍数核心在一个公式lcm(a, b) a / gcd(a, b) * b。为什么先除再乘因为a * b可能先溢出比如两个数都是十万级别的乘积就超过int上限了。先除以最大公约数把数缩小再乘b安全性高很多。我把答案变量开成long long稳妥处理更大数据。多组输入时注意循环结构每轮读入n如果读到EOF就退出。因为答案要累积所以每一轮都要把ans重新置为1。置为1并不是从零开始而是利用gcd(1, x) 1这个性质让第一轮正确的lcm就是x本身。#include stdio.h long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; } int main() { int n; while (scanf(%d, n) ! EOF) { long long ans 1, x; for (int i 0; i n; i) { scanf(%lld, x); ans ans / gcd(ans, x) * x; } printf(%lld\n, ans); } return 0; }2031进制转换用的是最朴素的短除法不断用原数除以进制R把余数存进数组最后倒序输出。要注意两个地方一是当R大于10时余数10、11、12要映射成A、B、C所以提前准备一个0123456789ABCDEF的映射表用余数下标直接取字符省去一堆if判断二是当输入N为负数时先记录符号再对绝对值做转换最后单独输出负号。很多新手会忘记特判N 0的情况。如果N是0短除法循环一次都不会执行结果数组是空的程序什么都不输出直接WA。我习惯在循环开始前写一行如果N为0直接输出0并跳过本次循环。#include stdio.h int main() { int n, r; char mp[] 0123456789ABCDEF; while (scanf(%d%d, n, r) ! EOF) { if (n 0) { printf(0\n); continue; } int neg 0, cnt 0, res[64]; if (n 0) { neg 1; n -n; } while (n 0) { res[cnt] n % r; n / r; } if (neg) printf(-); for (int i cnt - 1; i 0; i--) { putchar(mp[res[i]]); } printf(\n); } return 0; }3.2 时间进位与快速幂2033、20352033时间相加题目会给你两个时间的时、分、秒要求相加后按规则进位。这类模拟题最重要的是顺序先加秒秒数满60就进到分再加分分数满60就进到时最后时直接相加。我见过有人先算总秒数再反推时分秒也能做但直接按单位进位更符合直觉也更容易调试。需要注意输出格式里有Case字样这和某些只输出数字的题不同。每次循环的序号i从1开始输出Case i: h m s。小时部分不需要模60因为题目没有要求把超过24小时变成天数所以直接输出即可。#include stdio.h int main() { int n; scanf(%d, n); for (int i 1; i n; i) { int ah, am, as, bh, bm, bs; scanf(%d%d%d%d%d%d, ah, am, as, bh, bm, bs); int s as bs; int m am bm s / 60; int h ah bh m / 60; printf(Case %d: %d %d %d\n, i, h, m % 60, s % 60); } return 0; }2035是这段题里最值得花时间的一道。求A的B次方最后三位整数最直接的想法是循环乘B次但B可能非常大循环直接超时。快速幂的核心思想是把指数B按二进制拆开比如B11二进制是1011代表11 8 2 1所以A^11 A^8 * A^2 * A^1。这样只需要把A不断平方再按位选择要不要乘进结果里循环次数从B次降到log2(B)次。取模必须同步进行因为最后三位只和模1000有关。乘法过程中每次都对1000取模防止中间结果溢出。还有一个细节如果A是负数C语言里A % 1000可能是负数需要先加上1000转成正数再参与运算。#include stdio.h int main() { int a, b; while (scanf(%d%d, a, b) ! EOF) { if (a 0 b 0) break; int base a % 1000; if (base 0) base 1000; int ans 1; while (b) { if (b 1) ans ans * base % 1000; base base * base % 1000; b 1; } printf(%d\n, ans); } return 0; }很多人可能会问为什么不直接循环b次反正b也不算大问题就在这里OJ的测试数据不会按你想象的“不太大”来出一旦B超过十万循环就开始吃力超过百万、千万就直接TLE。快速幂的技巧不仅用于这题后面所有涉及大整数幂的题目都能用值得彻底吃透。4. 二维数组和集合操作的两道题4.1 杨辉三角的二维数组解法20322032杨辉三角是二维数组的经典入门题。底层规律很简单每行第一个和最后一个元素是1中间元素等于上一行对应位置和前一个位置之和写成递推就是a[i][j] a[i - 1][j - 1] a[i - 1][j]。只要把第一行初始化好后面的行都能推出来。我在写这类题时习惯把二维数组一次性全部初始化为0然后每行设置a[i][0] 1和a[i][i] 1这样既保证了边界也避免访问未初始化的内存。输出时每一行每个数字后面带一个空格组与组之间还要留一个空行。这个空行就是典型的“样例看不出来提交立刻PE”的埋伏点。#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { int a[32][32] {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 j 0; j i; j) { printf(%d , a[i][j]); } printf(\n); } printf(\n); } return 0; }4.2 A-B的集合思维20342034“人见人爱A-B”题目要求输出A集合中不在B集合里的元素并且从小到大排序。第一反应可以用两层循环暴力查找对A里每个元素遍历B看是否存在不存在就放进结果数组。这个做法的复杂度是O(n*m)在题目限定的数据规模下完全够用。排序用C标准库的qsort最省事排序后原样输出即可。这里还是输出格式的坑题目要求结果“每个元素后跟一个空格”所以我的输出直接是printf(%d , res[i])最后再补一个换行。如果结果为空输出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 (n || m)) { int a[128], b[128], res[128], cnt 0; for (int i 0; i n; i) scanf(%d, a[i]); for (int i 0; i m; i) scanf(%d, b[i]); for (int i 0; i n; i) { int flag 0; for (int j 0; j m; j) { if (a[i] b[j]) { flag 1; break; } } if (!flag) res[cnt] a[i]; } qsort(res, cnt, sizeof(int), cmp); if (cnt 0) { printf(NULL\n); } else { for (int i 0; i cnt; i) { printf(%d , res[i]); } printf(\n); } } return 0; }这道题如果想做得更优雅可以先对A和B分别排序然后用双指针做差集复杂度能降到O(nlogn mlogm)。但入门阶段我更推荐先暴力AC把“集合差集”这个语义理解清楚再考虑优化。5. 完整实操2035从读题到AC全流程5.1 读题与样例分析拿2035完整走一遍我的做题流程。题目要求读入两个整数A和B求A^B的最后三位整数多组输入遇到0 0结束。拿到这种题第一步不是写代码而是先把样例看懂。比如输入2 3期望输出是8因为8就一位它的后三位就是8本身。输入12 6期望输出是984这里就需要验算12^6算出来是2985984后三位确实是984。看样例能帮你确认三件事第一结果不补前导零8不输出成008第二输出的是整数形式第三0 0表示结束不是计算0的0次方。5.2 快速幂实现和取模快速幂的原理再展开一点。设指数B11二进制是1011从低到高每一位代表1、2、4、8。算法里用b 1判断最低位是否为1如果为1就把当前的base乘进答案。每处理完一位把base平方一次代表指数从2^0升到2^1、2^2、2^3然后把b右移一位。手动模拟一遍A3B3。初始ans1base3b3。第一轮b最低位是1ans13%10003base33%10009b右移变成1。第二轮b最低位是1ans39%100027base99%100081b右移变成0。第三轮循环结束输出27。3^327验证正确。取模运算贯穿始终因为(a * b) % mod ((a % mod) * (b % mod)) % mod每一步收紧值的大小既防止溢出也保证最后结果正确。5.3 提交与排错过程我第一次写这题的时候用的是最朴素的循环乘法本地测试小数据全对一提交就TLE。后来改成快速幂又踩了负数取模的坑当A为负数时A % 1000在C语言里结果是负数导致base为负答案直接不对。解决方法是在读入后立刻判断如果base小于0就加1000。还有一次错在结束条件上。题目的结束条件是a 0 b 0我写成了a 0 || b 0结果一组0 b的正常输入被当成结束符输出少了一大截。提交前把这类边界条件单独检查一遍真的能省很多时间。6. 常见问题与避坑实录6.1 输出格式过OJ的第一道坎我刷这批题的AC率很大程度上被格式问题拖累了。输出格式错误在OJ里叫Presentation Error简写PE意思是“你的结果对了但格式不对”。最常见的情况有四种多了一个空格、少了一个空行、空行位置不对、大小写写错。以这批题为例2034要求每个数字后带空格如果你只在数字之间加空格、行尾不加有可能PE2032和2027要求每组数据之间有空行如果你只在最后一组加空行或者遗漏空行也会PE。我的建议是提交之前先把题目原文的“Output”部分逐字读一遍再看样例输出有没有“看不见”的空行必要时把样例输出拷贝到文本编辑器里开启显示所有字符看看行尾到底有没有空格。6.2 输入读取gets、getchar和缓冲区2026、2027、2029、2030这几道题都涉及字符串读取缓冲区处理不好程序行为会非常诡异。最典型的场景是先用scanf(%d, n)读了一个数字然后想用gets读字符串结果gets读到了一个空行。原因很简单数字后面的换行符还留在缓冲区里。解决办法就是在scanf之后加一个getchar()把换行符消费掉。gets本身会把换行符读进去再丢弃所以用getchar吞掉残留换行后gets才能读到真正的下一行内容。需要注意的是部分OJ因为安全原因不再允许gets此时可以改用fgets(s, sizeof(s), stdin)但fgets会保留换行符需要手动去掉strcspn(s, \n)。6.3 数据类型与溢出入门选手最容易忽略的是int的表示范围。int最大大约是21亿也就是2.1×10^9一旦计算过程超过这个值就会溢出变成奇怪的负数。2028求最小公倍数时两个中等大小的数相乘就可能越过边界所以用long long是必须的并且在ans / gcd(ans, x) * x这里一定要先除后乘。2035快速幂对1000取模后数值不会太大但如果不取模直接乘base * base就可能溢出。养成“运算前估一下范围”的习惯能明显减少WA。频繁使用long long虽然会慢一点但在这类基础题里完全不是问题正确性优先。6.4 常见错误速查表错误现象可能原因解决方案2026只输出了第一个单词用scanf(%s)读了带空格句子改用gets或getline整行读入2027输出结果正确但PE每组数据后缺少空行每次输出结果后再补一个\n2028结果负数或明显异常int溢出使用long long并先除再乘2030统计结果偏大忘记汉字占两个字节统计负数个数后除以22031输出为空没有特判N0单独处理n0输出02032每行输出格式错数字间空格不统一每个数字后固定带一个空格2033结果完全不对进位顺序错误先算秒再算分再算时2035超时使用循环乘法改成快速幂按二进制位累乘这个表是我按实际调试过程整理的每题对应一个最可能的坑。刷题时如果WA了先对照这个表排查比盲改代码高效得多。我个人在实际操作中的体会是刷完2026到2035这一整段最大的收获不是“多了十道AC题”而是建立了三个习惯读题先看输入输出格式写代码前先考虑数据范围提交前先检查边界条件。这三个习惯在后面的每一道题里都在帮我省时间。尤其是2030和2035这两道一个让我第一次意识到编码层面的细节一个让我真正理解了快速幂的本质值回票价。如果你正卡在这一段题里别灰心慢慢磨AC之后回头再看你会觉得这套题设计得挺巧妙的。
返回列表