资讯动态

从杭电到华为:一次OJ刷题日志中的边界与细节

发布时间:2026/9/26 17:49:35 来源:尧图企业网站定制
3月11号晚上我照常打开题单准备刷几道OJ题。原本只是想热热身结果从杭电的入门题一路点到了某高校OJ的智能指针题从大数加法写到了字符串压缩从简单模拟调到了递归边界。那晚结束之后我忽然意识到这一整天的题单组合其实刚好串联起了一个很完整的观察OJ刷题这件事真正考验人的从来不是算法天赋而是你对待细节和边界的态度。这个标题里的3.11不是题目编号是我自己刷题日志里的一天。当时我一边刷一边把这天涉及的题号、平台、常见报错和思路全部记了下来后来整理成了一套可复用的刷题方法。这里就把3月11日这一天里我实际刷过的题、踩过的坑、以及从中学到的东西完整写出来给正在OJ入门、准备机试或者纯粹想系统训练算法基础的你一个参考。1. 3月11日题单的梯度设计从杭电到华为从水题到工程题先说我这一天实际接触的题单构成。热词里反复出现的杭电OJ、华为OJ、东方博宜OJ、湘潭大学OJ、江南OJ本质上都是同一个东西——在线评测系统只是归属的平台不同。杭电OJ在ACM圈子里属于元老级平台题量大、题目经典东方博宜OJ在信息学竞赛入门阶段很常用华为系OJ更多面向企业机试风格偏工程应用。它们共用一套提交代码-在线判题-返回AC/WA/RE/TLE的逻辑。我那天处理的题大概分成四个梯度题号/关键词平台题型难度定位杭电OJ 1002HDU大数加法高精度入门偏基础杭电OJ 1020HDU字符串连续字符统计入门偏字符串杭电OJ 1096HDU多case求和 输出格式入门偏格式东方博宜OJ 1065 / 1168东方博宜偏递归/数列/数组综合从入门到进阶的过渡华为OJ / 华为OD机试华为在线评测字符串处理、边界逻辑工程型题目西北农林科技大学OJ 智能指针高校OJ现代C语言特性课程型进阶题刚开始刷OJ的人最容易犯的一个错误就是觉得题目简单就等于可以秒杀。但实际上杭电OJ 1002这道经典题每年都有大量新手挂在输出格式上——算法本身五分钟能写完格式错误能卡你两小时。我3月11号的第一课就是简单题考察的不是智商而是你是否愿意静下心来把规则完整读完。刷题计划怎么安排才合理我的经验是不要一天刷十道不同类别的题而是一天一个专题。像我这一天虽然看起来跨了好几个平台但其实可以拆成三个专题高精度计算1002、字符串与格式控制1020/1096、进阶题型思维东方博宜的递归题、华为的风格题、智能指针题。一天处理三组不同类型的题目每组两到三道既不会疲惫又能让大脑在不同思维模式之间切换。时间安排上我习惯早上处理需要动脑的字符串题下午做数学向的高精度和递推晚上留给复盘和整理模板。2. 杭电OJ 1002大数加法真正难的不是加法是细节杭电OJ 1002A B Problem II大概是所有ACM入门者共同的记忆。题目描述很简单输入若干组大整数A和B求它们的和。表面上看就是加法但陷阱全在细节里。为什么大数加法这个知识点在网上被反复讨论因为计算机内置的整数类型有范围上限int大概到21亿long long到9×10^18而OJ题目里的A和B最长可以达到上千位。用内置类型根本存不下所以必须用字符串模拟手算加法的过程从个位开始逐位相加满十进一。整个过程像极了小学列竖式——这也是高精度算法最核心的思维模型。我3月11号当天写这个题时踩了一个非常蠢的坑。先说我最终通过的模板再解释每个关键点#include iostream #include string #include algorithm using namespace std; string addStrings(string a, string b) { string result ; int i a.size() - 1, j b.size() - 1; int carry 0; while (i 0 || j 0 || carry 0) { int sum carry; if (i 0) sum a[i--] - 0; if (j 0) sum b[j--] - 0; carry sum / 10; result.push_back(0 sum % 10); } reverse(result.begin(), result.end()); return result; } int main() { int n; cin n; for (int caseNo 1; caseNo n; caseNo) { string a, b; cin a b; cout Case caseNo : endl; cout a b addStrings(a, b) endl; if (caseNo ! n) cout endl; } return 0; }这段代码里有几个位置必须注意。第一为什么从字符串末尾往前遍历因为加法的进位是自低位向高位传递的字符串存储大数时第0位是最高位所以必须倒序遍历这才是竖式计算的原生顺序。第二为什么循环条件是i 0 || j 0 || carry 0因为两个数字长度可能不同较短的加完了之后还得继续处理较长的剩余位就算两个都加完了如果最后一步产生了进位这个进位不能丢。漏掉carry 0这个条件会导致999 1算出来少了最高位的1。第三结果在push_back过程中是从低位到高位排列的所以最后必须reverse。忘了反转字符串得到的就是一位一位反着的结果比如12 34 64之类的诡异输出。第四输出格式。杭电OJ 1002要求每组输出Case x:加一行算式且两组输出之间有一个空行但最后一组后面不能多出空行。这就是我用if (caseNo ! n) cout endl;的原因。我那天第一次提交WA就是因为在每组后面都无条件加了空行导致最后多打一个换行触发了Presentation Error。这种最后一个case不输出空行的规则在OJ系统里几乎是通行的潜规则大家一定要形成条件反射。还有一些细节值得展开说。比如加法的进位结果直接通过sum / 10取得sum % 10作为当前位这个方法比显式判断if (sum 10) carry 1更快更不容易漏。再比如字符和数字的转换a[i--] - 0是标准做法不要自作聪明用atoi一次只处理一个字符用减0最简单。我见过太多初学者在这些地方写出一堆冗余代码反而是错因。2.1 大数加法的常见报错和Debug思路我那天调试1002时系统返回过三种典型错误这里直接列出来供对照WAWrong Answer通常是进位没处理干净。测试用例试一下999 1、0 0、1000 9999分别看看结果是否正确。这三个用例能覆盖大部分进位和长度差问题。PEPresentation Error输出格式和答案只差在空行或空格。这种情况在OJ里最气人逻辑全对但格式不对。我的建议是完完全全逐字对照题目给的Output样例连空格数量都不要放过。RERuntime Error如果用了数组而不是字符串可能是数组开小了。1000位的数字至少要开1005长度的数组还要留一位给进位。字符串则没有这个问题。Debug大数题有个很实用的技巧不急着提交先自己写一个循环把1到9999里所有A B的加法全部跑一遍和内置类型计算的结果逐一对比。一旦有不一致立刻就能定位到进位逻辑的问题。这个思路我后来用在所有高精度题上AC率提升非常明显。3. 杭电OJ 1020和1096字符串压缩与输出格式考的全是边界热词里杭电oj 1020和杭电oj 1096都排在前列说明这两道题是很多人的共同记忆点。它们恰好代表了OJ刷题里最常见也最阴险的两类坑字符串边界处理和输出空行控制。3.1 杭电OJ 1020的连续字符统计1020这道题给一个字符串要求统计连续相同字符的数量并输出字符次数的形式。比如AABBB应该输出A2B3。核心逻辑不复杂遍历字符串遇到和上一个字符相同的就计数加一遇到不同的就把上一个字符的结果输出。但有一个边界特别容易翻车——字符串遍历到末尾时最后一个连续的字符段不会被结算。很多新手的代码长这样for (int i 0; i str.size(); i) { if (str[i] lastChar) { count; } else { cout lastChar count; lastChar str[i]; count 1; } }这个版本里如果字符串是AABB遍历结束后最后一段BB只更新了count却没有输出B2。正确做法是在循环结束后把最后一段单独补一次输出for (int i 0; i str.size(); i) { if (i 0) { lastChar str[i]; count 1; } else if (str[i] lastChar) { count; } else { cout lastChar count; lastChar str[i]; count 1; } } cout lastChar count; // 关键的一行我当时就是漏了最后一行白白交了两发WA。这类最后一个元素结算的边界在数组遍历、字符串处理、二分查找里都会反复出现。我的建议是写循环前先在纸上把长度为1和长度为2的用例走过一遍确认循环结束时的状态是什么再决定循环结束要不要补一段收尾逻辑。还有一个小细节题目里字符串是纯大写还是大小写混合决定了统计时是否要区分大小写。我3月11号刷到的版本明确要求按原字符统计也就是大写和小写当作完全不同的字符计数不能合并。这类题目要求一定要在动手前看清楚。3.2 多case输出空行的判断逻辑杭电OJ 1096属于经典的AB多case系列题目本身只是求和难点同样在输出每个case的结果占一行case之间要有一行空行但最后一个case后面不能带空行。解决方案是我在1002里已经用过的写法判断当前case是不是最后一个不是才输出空行。for (int i 1; i cases; i) { int a, b; cin a b; cout a b endl; if (i cases) cout endl; }就这五行代码每年卡住的初学者数量惊人。我记得有个统计数据这类多case格式题在所有OJ提交里的PE占比极高。原因是很多人脑子里只有每两个case之间空一行这个概念却忘了检验最后一行后面不能有换行这个隐藏规则。实际上做一个简单的归纳就能解决只要把空行看成是每个case前面的分隔符而不是每个case后面的输出内容写成除了第一个每个case前先输出一个空行就永远不会在结尾多出空行。if (i 1) cout endl; cout a b endl;我3月11号之后就把这个模式固定下来凡是遇到多case输出的题一律采用第一个不打印分隔之后的先打印分隔再输出内容的逻辑。这个思路在OJ之外也很有用——日志记录、报表输出、命令行的分隔符打印都是同一个套路。4. 东方博宜OJ 1065和1168从搜答案到拆思路热词里东方博宜oj答案1065和东方博宜oj答案1168这两个搜索词说实话让我有点感慨。东方博宜OJ面向的大多是信息学启蒙阶段的学生题目难度并不算高但题号1065和1168这两个位置恰好是从单纯的循环套用迈向综合思维的过渡区。搜答案的人多了说明很多人卡住的不是语法而是不知道题目在问什么。我必须诚实地说一句——我没有把这两个题号逐字背下来所以就不在这里默写题面了。但根据东方博宜OJ的出题习惯1065和1168这类编号中段的题目大概率落在递归数列、数组综合处理或者简单递推上。你要做的是打开题目把输入输出样例读三遍看清楚三个问题数据范围多大、边界条件是什么、是否存在多case输入。这三点清楚了思路往往就出来了。4.1 数列递推题的通用思维框架如果你现在正卡在某道东方博宜的递归或数列题上别急着搜答案先试一下这个框架。第一步把递推关系写出来。比如斐波那契类是f(n) f(n-1) f(n-2)阶乘是f(n) f(n-1) * n前缀和是f(i) f(i-1) a[i]。凡是能写出当前结果由之前结果计算得到的都是递推。第二步确定初始值。递推必须有一个起点比如f(0)0, f(1)1或者f(1)1。初始值缺失或错误是整个递推链条崩溃的最常见原因。第三步考虑是递归还是循环实现。递归代码结构清晰但n较大时会爆栈循环直接迭代没有这个问题。入门阶段我建议优先用循环等理解了状态转移之后再回头看递归会豁然开朗。// 通用递推模板从底往上算 // 假设f[i]表示第i项结果 vectorlong long f(n 1, 0); f[1] 1; // 初始值按题目要求设置 for (int i 2; i n; i) { f[i] f[i - 1] f[i - 2]; // 递推式按题目要求替换 } cout f[n] endl;这个模板能解的题远比你想得多爬楼梯、铺砖块、兔子繁殖、约瑟夫环的递推版本核心都是找到f[i]和之前项的关系。所以我看到东方博宜oj答案1168这种搜索词时第一反应不是把代码给出来而是把这类题的统一解法讲明白。一个能复制到十道题上的思路比一道题的完整代码有价值得多。4.2 为什么看思路比看答案管用我在3月11号的复盘里专门写下了一条搜答案搜来的代码你第二天就忘了自己推出来的思路三个月后还能用。这背后的原因是OJ题目的价值不在那个AC而在你大脑里建立的条件反射——看到数据范围很大想到高精度看到连续字符想到双指针或逐段统计看到递推关系想到边界与初始化。这一整套反射靠抄答案永远建立不起来。我的建议是给自己设一个20分钟规则一道题如果20分钟还没有任何头绪允许看题解但只看思路部分不看完整代码。然后用脑子里的思路自己写一遍写不出来的地方再回头对照。这个过程比直接抄代码痛苦但效果是立竿见影的。3月11号那天我处理东方博宜的题就是这么干的虽然多花了一个小时但那个递推边界问题从此再没犯过。5. 华为OJ和高校OJ的智能指针题刷题刷的不该只是AC如果只看杭电和东方博宜你可能会觉得OJ刷题就是算法题。但热词里华为oj华为oj编程以及西北农林科技大学c oj智能指针的出现说明OJ这个生态还有另一个重要分支——面向工程能力考核的在线评测。5.1 华为系OJ题型的三个典型特征华为系在线评测很多人习惯叫华为OJ和传统ACM题目的感觉不太一样。传统ACM题喜欢纯粹的算法和数据结构而华为系题更爱字符串解析条件分支状态模拟的组合拳。我总结下来有三个典型特征输入格式复杂一行里有多个字段中间可能有逗号、分号、方括号需要自己写解析逻辑。边界条件特别多题目会把一种边界情况包装成完整的故事背景你要在细节里找到真正的判定条件。输出要求极其严格不仅结果要对格式、顺序、甚至相同结果时的输出先后都有讲究。应对这类题我建议养成一个习惯先搭好输入处理框架再写业务逻辑。把数据一行一行读进来做结构化再进入判断流程。很多人一上来就写核心逻辑结果输入都没接住后面全白搭。5.2 OJ题里到底该不该用智能指针热词西北农林科技大学c oj智能指针值得单独聊聊。高校OJ把智能指针放进题目说明计算机基础教育已经开始从中级C迈向现代C了。智能指针std::unique_ptr、std::shared_ptr、std::weak_ptr解决的核心问题只有一个内存泄漏和悬垂指针。#include memory std::unique_ptrint p std::make_uniqueint(42); // 离开作用域时自动释放内存不需要手动delete std::shared_ptrint sp1 std::make_sharedint(100); std::shared_ptrint sp2 sp1; // 引用计数为2全部离开作用域后释放内存在纯算法题里智能指针确实用得不多——算法题一般不需要你管理堆内存数组和容器就够用了。但在课程作业、工程项目和华为这类偏应用的机试题里智能指针考察的是你有没有资源管理的意识。new了之后忘了delete就是典型的C内存泄漏而unique_ptr直接把所有权表达得清清楚楚代码既安全又容易读。我3月11号刷到智能指针相关题时第一反应是这题怎么不在算法范围里。后来想明白了很多高校的OJ不只是算法训练平台还是课程验收工具。程序设计课教到智能指针OJ自然会出现对应的题目。如果你现在要考这类课程题建议把三件事练熟make_unique和make_shared的标准写法、所有权转移std::move的语义、以及shared_ptr循环引用时如何用weak_ptr打破环。这三个知识点是智能指针题最常见的考点。从工程能力角度看华为OJ和智能指针题其实指向同一个信号现在的机试不再只考你会不会算法也开始考你写出来的代码放在真实项目里靠不靠谱。所以我的态度很明确——刷题时多想想代码的健壮性和可读性适度使用现代C特性这些习惯在机试里不会吃亏在真实项目里更是直接受益。6. 3月11日复盘清单一套可以反复使用的刷题沉淀方法每次刷完题真正决定你进步的环节是复盘。3月11号晚上我做了三件事整理错题表、提炼可复用模板、标记需要二刷的题目。下面是我几次迭代后固定下来的复盘格式直接抄作业就能用。6.1 错题复盘表格我习惯在本地维护一个Markdown表格每道错题占一行字段如下题号错误类型根因分析解决方案同类题标记HDU 1002PE最后一个case后多输出了空行采用第一个不打印之后先分隔再输出所有多case输出题HDU 1020WA字符串末段未结算循环结束后补一次输出所有分段统计类题东方博宜XXXWA递推初始值设错先手算前3项验证递推式所有递推题这个表最重要的不是记录错误本身而是把错误归类。我做了几个月之后发现常见的OJ错误其实就那么几类进位/借位没处理干净、边界元素未结算、输出格式不符合规范、数组越界、数据范围超过int。当你发现错题在这五类里反复出现就知道自己该针对性地练哪一块了。6.2 三次重复策略从哪看到这个我不记得了但实践下来确实有效一道做错的题当天晚上不看代码重写一遍三天之后再写一遍一周之后再写一遍。三次都能独立通过才算真正掌握。这个策略看起来很笨实际上比盲目刷十道新题高效得多。因为每次重写你的大脑都会重新走过一遍当时的bug和修复路径把错误模式和正确解法牢牢绑在一起。3月11号那天我特别注意了这一点——当时有两道题卡到晚上十点半也没完全AC换了以前我可能就直接看题解抄过去了。但这次我忍住了把题号和我的思路写进表格标记为待二刷。三天后我重新打开那两道题果然还是ak一个但这次调试只花了十五分钟就通过了。那种感觉比一百次搜到答案都爽。6.3 关于搜OJ答案这件事的最后一点体会打从开始写这篇东西我就一直在想东方博宜oj答案1065这类搜索词背后的人到底需要什么。说实话谁没有搜过答案呢我自己在入门阶段也没少搜。但我渐渐发现真正让我进步的从来不是复制下来的那段AC代码而是我在理解代码时想通的那个为什么——为什么这里要加一为什么那里要从后往前扫为什么循环结束之后还要补一次处理。所以如果你正卡在某个OJ题号上我的建议是先只搜题目大意和思路不看代码。自己写一遍卡住了再去搜半个代码片段然后继续写。这个过程中你获得的东西远比一个AC记录值钱。3月11号这个日期在这篇日志里已经不只是一个时间点了。它是我把刷题从任务变成方法的一个锚点。杭电的格式题教会我谨慎东方博宜的递推题教会我把问题抽象成模型华为OJ的工程型题目教我关注代码的健壮性智能指针题提醒我别只盯着算法数据结构忘了语言本身也在进化。下一次你再看到某个日期配上OJ三个字母别只想到今天又要提交作业试着把这一天当成一次完整的训练切片——认真做一遍认真复盘一遍认真记下来。这比一次多刷十道题的收获要大得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑