news 2026/9/13 3:29:38

东方博宜OJ 1201-1210逐题解析:语法收尾与算法启蒙

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
东方博宜OJ 1201-1210逐题解析:语法收尾与算法启蒙

东方博宜OJ刷到1201-1210这段的兄弟,多半是在补课程作业,或者正在为CSP-J、蓝桥杯这类入门比赛做准备。说实话,这段题目放在整个OJ题库里不算难,但它刚好卡在语法入门尾巴和简单算法开头的连接处,特别容易在细节上翻车:少打了个空格、数组开小越界、循环边界差了一位,这类低级错我见得太多了。

这篇我就把这10道题按最常见的版本逐个拆开讲,每道题给出完整思路、参考代码和易错点。我不太建议直接抄代码交差,因为OJ的判题系统会查重是一回事,更关键的是——这段题几乎是后面所有题目的地基,你在这里踩过的坑,后面都要加倍还回去。

1. 先看清这组题:1201-1210到底在考什么

1.1 题号背后的训练节奏

东方博宜OJ的题号编排有个规律,基本上是按照难度阶梯排的。1000出头还在练基本的输入输出、变量类型,1100多开始上分支和循环,到了1200这一带,就明显感觉到题目开始"混搭"了。不是说单纯考一个知识点,而是要把几个基础点串起来用。

1201-1210这段,我的判断是正处在"语法基础收尾、算法思维启蒙"的过渡区。题目里既有循环数学题,也有数组字符串操作,还开始涉及到递推、排序、进制转换这些简单算法。你把它当成一个承上启下的关卡来看,就明白为什么学校老师喜欢从中抽题当作业了——它能把前面学的知识全部串一遍,又能试探一下你有没有算法思维。

1.2 题型分布与难度雷达

我按东方博宜OJ里这一段的常见考法,把10道题大致归类如下。注意不同学校或平台挂的题目版本可能有细微差异,有的把输出大小写改了,有的把数据范围扩大了,但核心考点基本就是这个分布。

题号常见题型核心考点最容易出错的地方
1201数列求和循环、等差数列int溢出、边界值
1202最大公约数辗转相除法特殊输入、除零
1203回文数判断数字拆分输出大小写
1204数字反转循环取位负号、前导零
1205数组逆序数组、循环数组越界、空格格式
1206单词统计getline、状态机换行符残留
1207素数筛选埃氏筛数组初始化、超时
1208斐波那契数列递推递归爆栈、首项定义
1209排序sort或冒泡升降序要求
1210进制转换短除法0的特判、倒序输出

难度都不算高,但如果有一两道题卡住,大概率不是不会写,而是漏了某个边界情况。下面我按顺序逐题拆。

2. 逐题拆解与参考代码(上):数学与循环基础

2.1 1201 数列求和:公式比循环更快

这道题最常见的版本是:输入一个整数n,求1+2+3+...+n的值。很多同学的第一反应是写个for循环累加,逻辑没错,但如果n给到10的9次方量级,循环就得跑十亿次,轻则超时,重则让你怀疑人生。

正确做法是直接用等差数列求和公式:n * (n + 1) / 2。但这里藏着一个新手必踩的坑——直接写int n; cin >> n; cout << n * (n + 1) / 2;,n一大就溢出了。因为两个int相乘的结果还是int,存不下就可能变成负数。

#include <iostream> using namespace std; int main() { long long n; cin >> n; cout << n * (n + 1) / 2 << endl; return 0; }

我自己给学生讲这道题时,一定会让他们记住一句话:只要题目里的数可能超过10的9次方,就老老实实用long long,别自作聪明。另外这个公式在n是奇数或偶数时都能整除,不用担心除不尽的问题,因为n和n+1里必然有一个是偶数。

2.2 1202 最大公约数与最小公倍数:辗转相除法的边界

这道题的核心算法就是辗转相除法(欧几里得算法),思路一句话就能讲清楚:两个数a和b,a除以b的余数为r,然后让a=b,b=r,不断重复,直到余数为0,此时的b就是最大公约数。代码实现也很短:

#include <iostream> using namespace std; int gcd(int a, int b) { while (b != 0) { int t = a % b; a = b; b = t; } return a; } int main() { int a, b; cin >> a >> b; cout << gcd(a, b) << endl; return 0; }

如果题目还要求输出最小公倍数,记住一条:先除再乘,a / gcd(a, b) * b,而不是a * b / gcd(a, b)。原因是a乘以b可能会溢出,先除以最大公约数可以把这个风险降下来。还有一点容易被忽略,就是a和b谁大谁小其实无所谓,辗转相除法第一轮自己会调整,不需要提前比较。

2.3 1203 回文数判断:拆数字的通用套路

回文数的定义不用多解释了,121正着读反着读都一样。判断方法有很多,最通用的是把数字一位一位拆出来,然后组成一个新数,最后比较新数和原数是否相等。

#include <iostream> using namespace std; int main() { int n, x, rev = 0; cin >> n; x = n; while (x > 0) { rev = rev * 10 + x % 10; x /= 10; } if (rev == n) { cout << "Yes" << endl; } else { cout << "No" << endl; } return 0; }

拆数字的套路就一句话:"取余拿末位,整除去末位"。x % 10拿到当前最后一位,x /= 10把最后一位删掉,然后拼到rev后面。这里特别提醒一句:输出到底是Yes还是YES,是No还是NO,以题目原文为准。我见过不少人被这种细节卡到怀疑人生。

另一种思路是把数字转成字符串再头尾比较,实现更直观,但数字拆分法不需要引入字符串库,也更能锻炼对整数运算的理解,建议两个做法都练一遍。

2.4 1204 数字反转:负数和零怎么处理

数字反转和判断回文其实是同一套核心逻辑,只是这道题会多出几个特殊情况。最常见版本是输入一个整数,输出它的反转结果,比如输入-120,输出-21(注意不是-021,前导零要去掉)。

#include <iostream> using namespace std; int main() { int n; cin >> n; bool isNegative = false; if (n < 0) { isNegative = true; n = -n; } int rev = 0; while (n > 0) { rev = rev * 10 + n % 10; n /= 10; } if (isNegative) { rev = -rev; } cout << rev << endl; return 0; }

新手最容易踩的坑就是只处理正数,题目一输入-120就懵了。还有一点要注意,如果输入的是0或者像100这类反转后开头为0的数,用整数算法天然会把前导零丢掉,输出1而不是001,这恰好符合大多数题目的要求。

3. 逐题拆解与参考代码(下):数组、字符串与简单算法

3.1 1205 数组逆序输出:数组开多大的学问

这道题一般是输入n和n个整数,然后逆序输出。很多人第一个反应就是:"我正着存,倒着遍历不就行了?"对,这就是全部思路。

#include <iostream> using namespace std; int a[1005]; int main() { int n; cin >> n; for (int i = 0; i < n; i++) { cin >> a[i]; } for (int i = n - 1; i >= 0; i--) { cout << a[i]; if (i > 0) cout << ' '; } cout << endl; return 0; }

这里有两个细节值得花时间强调。第一,数组到底开多大?我见过太多人开a[105]然后题目范围给到1000,直接越界。稳妥的习惯是看完题目范围后,多开10到20个,比如题目说n不超过1000,就开a[1005]a[1010]。第二,输出格式。OJ判题时对空格和换行非常敏感,用我上面写的if (i > 0) cout << ' '这种写法,可以保证最后一个数字后面没有多余空格。

3.2 1206 单词统计:getline与状态机

这题常见版本是:输入一行英文句子(可能包含多个连续空格),统计有多少个单词。核心难点在于:如果只用cin >> s,空格会被自动跳过,反而没法处理。正确姿势是用getline读取整行,然后用一个"是否正在单词中"的标记来统计。

#include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s); int cnt = 0; bool inWord = false; for (char c : s) { if (c != ' ') { if (!inWord) { cnt++; inWord = true; } } else { inWord = false; } } cout << cnt << endl; return 0; }

这个方法叫状态机思想,通俗说就是用一个变量记住"我当前是不是在一个单词里面"。碰到非空格且之前不在单词里,说明新单词开始了;碰到空格就把状态重置。这个套路以后在字符串题目里会反复用到,值得重点掌握。

有一个经典坑:如果题目前面还有一次cin >> n之类的操作,再用getline会直接读到残留的换行符,导致字符串为空。解决办法是在cin之后先调用一次getline(cin, 临时字符串)把换行吃掉。

3.3 1207 素数筛选:O(n log log n)背后的原理

求2到n之间的所有素数,最朴素的做法是对每个数逐个判断,复杂度是O(n√n),n小的时候无所谓,n一旦到10的5次方以上就会明显变慢。这里推荐用埃拉托斯特尼筛法,简称埃氏筛,思路特别形象:准备一张从2到n的表,从2开始,把2的所有倍数划掉;然后找到下一个没被划掉的数3,把3的所有倍数划掉;不断重复。

#include <iostream> using namespace std; const int MAXN = 1000005; bool isPrime[MAXN]; int main() { int n; cin >> n; for (int i = 0; i <= n; i++) isPrime[i] = true; isPrime[0] = isPrime[1] = false; for (int i = 2; i * i <= n; i++) { if (isPrime[i]) { for (int j = i * i; j <= n; j += i) { isPrime[j] = false; } } } for (int i = 2; i <= n; i++) { if (isPrime[i]) cout << i << ' '; } cout << endl; return 0; }

很多教材会直接给代码,但不解释为什么内层循环从i * i开始。原因很简单:比i * i小的i的倍数(比如2 * i, 3 * i ...),在之前更小的素数筛选时已经被划掉了,没必要重复操作。这个优化能把常数压下去不少,而且代码看着也更专业。

如果你把isPrime定义成局部数组,记得先初始化;定义成全局数组则可以默认全为false,但逻辑上还要自己维护。我上面特意写成了全局数组再手动赋true,图的就是逻辑直观。

3.4 1208 斐波那契数列:递推别用递归

斐波那契数列的定义是F(1)=1,F(2)=1,从第3项开始每项等于前两项之和。代码实现最简单的是递归,但n一大就会原地爆炸,因为重复计算太多了。别问我怎么知道的,我曾经让刚学的学生用递归求第50项,半天没跑出来。

正确的入门做法是递推,用一个循环从第3项一路算到第n项:

#include <iostream> using namespace std; int main() { int n; cin >> n; long long a = 1, b = 1, c; if (n <= 2) { cout << 1 << endl; return 0; } for (int i = 3; i <= n; i++) { c = a + b; a = b; b = c; } cout << b << endl; return 0; }

这道题有个特别坑的地方:有些题目版本里F(0)=0、F(1)=1,有些则是F(1)=1、F(2)=1,两者输出的结果在n比较小时完全不一样。所以做题第一件事是看题目给的定义和样例,先拿样例验证你理解的规则对不对,再动手写代码。

3.5 1209 排序:sort能用,但底层逻辑得懂

排序题在OJ里属于"会了不难,难了不会"的类型。如果平台用的是C++,直接用sort函数就能轻松搞定:

#include <iostream> #include <algorithm> using namespace std; int a[1005]; int main() { int n; cin >> n; for (int i = 0; i < n; i++) cin >> a[i]; sort(a, a + n); for (int i = 0; i < n; i++) { cout << a[i]; if (i != n - 1) cout << ' '; } cout << endl; return 0; }

默认是升序,如果题目要求降序,加上第三个参数greater<int>()就行。不过我也想提醒一句:如果你是在课程作业里,有些老师会明确规定不能用sort,必须手写冒泡或选择排序。这时候你就得理解排序的底层逻辑,比如冒泡排序的核心——每轮把相邻的两个数比较并交换,把当前最大的数"冒"到最后面。

手写冒泡的代码我就不贴了,网上到处都是,关键是理解:外层循环控制多少轮,内层循环控制比较范围,每轮结束后比较范围缩小一格。这个思想比背代码重要一百倍。

3.6 1210 进制转换:短除法倒着读

十进制转二进制,标准做法是短除法:不断除以2,记录余数,最后把余数倒着拼起来。比如10转二进制,10除以2余0,5除以2余1,2除以2余0,1除以2余1,倒着读就是1010。

#include <iostream> #include <vector> using namespace std; int main() { int n; cin >> n; if (n == 0) { cout << 0 << endl; return 0; } vector<int> bits; while (n > 0) { bits.push_back(n % 2); n /= 2; } for (int i = bits.size() - 1; i >= 0; i--) { cout << bits[i]; } cout << endl; return 0; }

这里最容易被忽略的是n等于0的情况,直接输出0,不然后面的while循环压根不进去,啥也输出不出来。另外如果题目要求转成八进制或十六进制,代码只需要把n % 2n / 2改成对应的基数,但十六进制要考虑10到15用A到F表示,这时候用vector<int>存余数再按需转字符会比较方便。

4. OJ判题结果与常见错误排查:从WA到AC

4.1 判题结果怎么看

我在带新手刷题时发现,很多人看到WA就懵了,不知道自己到底错在哪。先把这个表记住,遇到问题起码能判断排查方向。

判题结果含义优先排查方向
AC全部通过恭喜,下一题
WA答案错误算法逻辑、边界情况、输出格式
PE格式错误空格、换行、大小写
RE运行时错误数组越界、除零、递归过深
TLE运行超时换更高效的算法、检查死循环
MLE内存超限数组开太大、递归爆栈
CE编译错误语法问题、忘记引入头文件

其中WA和PE看起来像,但性质不同。PE说明OJ已经拿到了你的输出,只是格式不对,比如最后多了一个空格。很多判题系统会把PE单独列出来,就是为了提醒你"逻辑基本对了,改改格式就行"。

4.2 这段题最容易翻车的5个细节

第一个是输出格式。我见过太多人,代码逻辑完全正确,就因为最后一个数后面多打了一个空格,反复提交几十次。养成好习惯:要么像我上面那样判断不是最后一个就输出空格,要么用字符串拼接好再一次性输出。

第二个是整数溢出。1201的n * (n + 1) / 2就是个典型,n刚过10的5次方,int就扛不住了。判断要不要用long long,就看题目给的数据范围,只要接近10的9次方,直接上long long没商量。

第三个是数组越界。数组开小了不会马上报错,只会在运行时踩到不该踩的内存,表现出来就是各种莫名其妙的RE。直接养成习惯:数组按最大值再加10或20。

第四个是循环边界。i <= n还是i < ni * i <= n还是i * i < n,这类问题几乎每道题都会出现。我的习惯是代入特殊值验证,比如n=1或n=2时程序还正不正常。

第五个是特殊输入。n=0、n=1、输入为0、输入为负数,这些边界值最容易让代码漏出破绽。样例通常给的是正常值,你自己得多测几个边界样例,这个习惯是真的能救命。

5. 刷完这段题,下一步怎么练

5.1 同题型的变式与扩展

1201-1210是个很好的起点,但你想真正把基础打牢,光做这10道题不够。我自己刷题的经验是:每道题做完之后,给自己出几个变式,练到看到题目就知道考点在哪的程度。

比如数列求和之后,你可以去练平方和、立方和、分数数列求和;最大公约数之后,可以练三个数的最大公约数、最小公倍数应用的日期问题;数组逆序之后,可以练循环右移、数组去重、合并两个有序数组。这些题目在OJ上一搜一大把,每个知识点扩充3到5题,你的手感会完全不一样。

5.2 提交前自测清单

我总结了一个提交前的自测清单,每次提交之前花30秒过一遍,可以省掉大量提交试错的时间。

第一,样例能过吗?样例过了不代表AC,但样例都过不了肯定没戏。第二,边界值测了吗?n=0、n=1、n取最大值、输入负数,这些特殊输入跑一遍。第三,输出格式检查了吗?最后一行的换行、最后一个数字后的空格、大小写、中英文括号,全部对一遍。第四,数据类型对吗?会不会溢出,要不要用long long。第五,数组够大吗?有没有多开余量。

这套清单看着简单,真能坚持下来的人不多。我见过太多人,样例一遍过、自信满满提交,结果WA得一脸茫然,回来一查,不是少了个等于号,就是数组开小了。

说说我自己带学生刷这套题的一点体会。很多人以为OJ刷题就是比谁代码敲得快,其实不是。做得快的同学,往往是花时间读题、在草稿纸上推演、想清楚边界条件之后才动手敲键盘的。1201-1210这10道题,每道题都不难,但它们组合起来就是在训练你一件事:把一个模糊的题目,转化成清晰的、可运行的逻辑。

另外分享一个小技巧,刷完每道题之后,不管AC没AC,都在自己的笔记里记一行:这题考了什么知识点,我第一遍漏了什么条件,以后遇到同类题要提醒自己注意什么。积累一段时间你就会发现,犯过的错基本不会再犯第二次。这比刷题数量重要太多了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 3:26:08

延续预训练(CPT):重塑大模型行业认知基座的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:25:42

Cursor深度测评:从Tab补全到Agent模式,值不值得开会员?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:22:48

Java+DDD复刻Deepseek Harness:大模型工具调用与Agent编排实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华