小红书文章

2025 CSP-J/S挂分与爆零原因总结

挂分总结:

  • 上午考完了 J 组,下午 S 组命名文件夹时还是写的 J。
  • 文件夹提交错误。
  • 没开文件读写或文件读写写错了,比如应该写 club.out 但是写成了 club.ans。建议本地就全部用文件读写进行测试。
  • 最后 10 分钟对做法进行了较大的调整,但没有改完/改错了。
  • Windows 系统能编译运行,但 NOI Linux 中编译错误导致 0 分(GCC 版本不同)。建议在 NOI Linux 中使用 PDF 第一页给出的编译参数去编译运行一下代码,尤其注意加上 -std=c++14-O2,这两个很容易触发编译错误或一些写代码时不小心写出的奇妙 bug。
  • 写了多个部分分,但是根据数据范围判断调用哪个部分分做法时写错了,或单独测试某个做法的代码忘了删。
  • memset 清多组数据测试导致 TLE,但本地测试未构造相关数据,所以没有发现。
  • MLE,比如 S 组 T2,无脑 #define int long long 后开了很大的数组,超过了 512MB 的内存限制,本地和各大 OJ 测试没问题,CCF 评测机测试直接 0 分。(哥哥:别 TM 再 #define int long long 了)。
  • 数组开小或访问越界,比如 S 组 T1 开了 3 个优先队列,但用到了下标 3;比如 S 组 T2 点数只开了 n 而不是n + k
  • 无穷大开的不够大,比如 S 组 T2 的最小生成树。
  • 漏看/看错条件,导致本地测试(包括对拍)时构造的数据有明显的情况没覆盖到,例如 S 组 T3 只看到了 len(s1) = len(s2),但没意识到没有保证 len(t1) = len(t2)
  • CCF 评测机性能与各大 OJ 以及考试机的性能存在差异,导致某些感觉能通过的测试点实际上在官方测试中会超时,本次考试的重灾区在 S 组 T2 上,不少同学洛谷 100 分但官方测试 80 分,一部分原因是觉得只路径压缩可过,但实际上还是要加上启发式合并。以及,有的人写了复杂度更优的做法,但常数太大导致官方数据没过。挑战竞选CSP-S T2最复杂做法,心态爆炸求助 - 洛谷
  • 字符串运算复杂度错误,s += t 复杂度是 O(t)O(|t|)s = s + t 复杂度是 O(s+t)O(|s| + |t|),后者在连续做拼接时会被卡成平方复杂度。
  • 在 NOI 目录下和桌面都有代码文件夹,但老师收代码时收错了

反向挂分(指实际的分数比估计的分数明显要高)总结:

  • T2 O(2km)O(2^km) 甚至 O(2kmlogm)O(2^km\log m) 在 CCF 评测机上能通过官方数据,可能是因为这是一道图论题,数据很容易造水,字符串题也是类似的,数据容易造弱。

特别是那种结果出来很烂但是说本来考得特别好的,真正有实力的选手成绩很多都是比较稳定的,很少存在挂大分一说吧,你要真是挂成这样的,那你不能否认,就是你这方面能力不足;

感觉有些人像是那种平时一直吹自己有多厉害
然后打不出成绩
于是就开始四处宣传自己的神秘错误
说自己本来能考多高分的那些人

CSP-J/S 考前注意事项

《CSP-J/S考前最后5天,过来人的超全后勤检查清单》

距离 CSP-J/S 还有 5 天,到了这个阶段,选手的知识量和解题能力已经不太能再有明显的提高,比拼的早已不是知识量,而是谁的准备更充分、心态与发挥更稳定。

作为一个过来人,我想说,考前的很多细节,很可能决定了你考场内的发挥。 今天这篇笔记,我们不会聊很硬核很技术的话题,只聊那些能让你安心应试、避免非智力因素减分的后勤大事,文章同样是干货满满!

考前一周——状态调整期

核心目标:将身心调整至最佳竞技状态。

  1. 身体健康是第一要务
    • 饮食:清淡为主,避免生冷、油腻、不洁的食物,严防肠胃炎!一个真实的惨痛案例是,我的哥哥高考当天,家里给他炸了很多肉串,本意是想让他吃顿好吃的,结果下午的数学考场上拉肚子去了两次厕所,耽误了 10 分钟。
    • 作息立即开始调整生物钟! 按照考试时间安排作息,比如你是 J 组则按照 8:30 考试的标准安排作息,并预留好吃早餐+路上奔波+提前 45-60 分钟进考场试机的时间,S 组则是按照下午 2 :30 考试做准备。
    • 穿衣:目前北方已经有了明显的降温,这几天要注意保暖,多关注天气预报,出门宁可穿厚了再脱也别穿薄。另外近期流感比较多,如果要去公共场所,尽量佩戴口罩,如果身边有感冒的,能远离则远离。
    • 运动:进行适度的温和运动,缓解压力,但避免剧烈运动导致受伤或过度疲劳,尽量避免大汗淋漓着凉感冒。
  2. 回顾错误,保持手感
    • 停止刷难题:不再挑战难题、怪题,以免打击自信。
    • 回归平时记录的错题和好题:快速回顾过往的错题和笔记,巩固易错点。
    • 日常训练:每天在接近考试时间的时候做几道题,比如假如你是 S 组,则在下午 2:30 到 6:30 之间拉一套模拟赛,或不想那么累的话就选两三道题做一下,让自己的身体适应在这个时间做题。做任何题时,要尽量按照考场的纪律去要求自己,不要边听歌边训练,不要水群和交流,不要抄代码模板,不要上网查资料。

考前一天——最终准备日

核心目标:消除一切不确定性,安心待考。

  1. 实地勘察,熟悉路线
    • 计算时间:亲自走一遍从家到考场的路线,计算时间,第二天需要预留出比预估多50%的时间,以应对堵车等突发状况。
    • 定位考场:找到具体的教学楼、教室和座位(如果允许),避免当天匆忙寻找。如果不能进入寻找,则明天要预留好找位置的时间,有的考点很大可能确实需要走一段路。
  2. 准备“考试包”,一件件核对
    • 必带证件:准考证、身份证(或学生证),以及本省要求的其他材料,具体需要看本省在 NOI 官网上发的通知。建议纸质版的材料多复印几份,放到书包的多个地方。
    • 饮食补给
      • 早/午餐:提前想好第二天考试前的早午饭吃什么,最好是平时怎么吃就怎么吃。
      • 考场补给:带一瓶水(有的考场要求撕掉标签)、巧克力/士力架等小体积高能量零食,在考试中途快速补充体力。
    • 个人物品:眼镜、外套、纸巾等。
  3. 心理建设与放松
    • 积极暗示:告诉自己“我已经准备得很充分了”。
    • 避免讨论:不要和同学讨论难题或猜测考题,徒增焦虑。
    • 放松身心:听听音乐、看看闲书,晚上用热水泡脚,促进睡眠。

考试当天

核心目标:平稳发挥,将准备转化为分数。

  1. 早晨起床后
    • 吃一顿舒适的早餐,但不宜过饱。
    • 最后检查一遍“考试包”。
    • 再次确认准考证、身份证已带齐。
  2. 进入考场后
    • 上厕所:开考前务必去一次洗手间。
    • 调整考位环境:最主要的一点是把主机的位置调整一下,有时候容易被膝盖或者脚踢到导致关机,如果自动还原系统则可能导致考试时程序丢失。
    • 调试环境:尽快熟悉编程环境(IDE),测试简单的编译、运行与调试,Windows 和 NOI Linux 都要试,如果是虚拟机的话格外要关注怎么在虚拟机和物理机之间传文件。
    • 模板编写:上面的事情都做好后,如果允许的话,可以写一点自己常用的头文件,代码架构等,如果要对拍可提前写好对拍框架。
  3. 考试进行中
    • 全局观念:拿到试题后,花 5-10 分钟快速浏览所有题目,初步判断难易,制定做题策略。
    • 时间管理:严格遵循自己考前制定的策略,不要战时轻易大幅度改变答题策略。由于不同的人有不同的策略以及目标分数,所以这里不对策略做任何推荐。
    • 突发事件预期:考场的人不一定和平时机房小伙伴一样,可能旁边的人答题时很吵,这时候要想办法及时调整自己的状态,必要时求助监考老师(不管有没有用,你都可以去求助一下)。
    • 最后检查:留出至少15分钟,检查文件名、文件读写、数组大小、样例能否通过等可能严重影响成绩的问题。

同学们,CSP-J/S 只是漫长竞赛路上的一个节点。请相信,你为它付出的每一滴汗水,都已在暗中为你积蓄了力量。现在,你需要做的只是放松、信任自己,然后从容地走进考场,去收获属于你的果实。

最后,祝大家都能稳定发挥,如愿取得理想的成绩! 如果觉得这份清单有用,欢迎点赞收藏,转发给需要的小伙伴哦!

学信息学奥赛,到底要学哪些数学?(附各阶段学习路线)

很多刚接触信息学奥赛的学员和家长常会问:学信奥要不要提前学高中数学?甚至大学数学?要不要去学数学奥赛?

作为一个过来人,我想说:盲目地提前学高深数学,效率极低。 信奥需要的是一套 “精准对口”的数学知识图谱,它和校内数学、数学奥赛的侧重点有本质不同。

本文将为您详细拆解,从入门到 CSP-S/NOIP 阶段,学生必须掌握建议了解的数学知识点,并给出明确的可以用来学习的资料。

我们下面需要有一个讨论基础:假设您或您的孩子至少已经掌握了所有小学数学的内容。没有这个基础的话我的建议是先想办法把小学数学快速学完,暂时不要学信息学奥赛。

一、核心先行:贯穿信奥的五大数学思想

在接触具体知识点前,首先要建立正确的思维方式。这五大数学思想是解所有算法题的底层逻辑:

  1. 函数思想:将问题中的关系抽象成函数。这是理解变量、输入输出和程序模块化的基础。
  2. 方程思想:将题目约束条件转化为方程或不等式,这是对问题建模和求解的起点。
  3. 数形结合思想:善用图形辅助思考。例如,在平面直角坐标系上画函数图象,或将图论问题可视化。
  4. 分类讨论思想:信奥题中大量存在“如果...否则...”的逻辑,严谨地划分所有可能情况,是避免遗漏的关键。例如组合计数先分类后分步、具有多条转移方程的动态规划等。
  5. 化归思想:将复杂陌生的问题,转化为熟悉的基本模型。这是解决新问题的有效策略。

核心提示: 低龄学员不必刻意学习这些概念和思想,家长也不必焦虑,但教练应在日常教学中通过例题不断渗透和强化这些思想。

二、信奥必备数学知识点清单(S 组以内,按优先级排序)

以下知识点按“紧急和重要”程度分为三个梯队,请根据学生当前目标按需学习。

T0级别:基础工具(无此基础,好多题目都读不懂)

  • 时空复杂度分析:评估算法效率的标尺,懂这个才知道自己写出来的程序能运行多快。来源:任何一本算法入门书的前几章。
  • 进制、进制转换与位运算:特别是二进制相关的内容,很多算法的设计思想以及题目的求解思路都是基于二进制的。来源:算法书籍或在线教程。
  • 集合论基础:理解集合关系、集合运算相关的概念和符号。来源:看高中数学必修一即可,也可以稍微阅读一下离散数学的集合论部分。
  • 基本初等函数:指数、对数、幂函数、三角函数等,重在理解其增长速度的特性(例如对数增长很慢,指数增长很快)。来源:高中数学必修一、必修四。
  • 数列与递推:主要是数列的概念、等差和等比数列的通项公式、递推公式、求和公式,以及使用递推求解问题的思想。来源:高中数学必修五。

T1级别:核心考察内容(J 组和 S 组中数学题最常考的内容)

  • 数论基础:包括整除、素数、素因数分解、素数筛、最大公约数(gcd)、同余、费马小定理、快速幂、逆元、中国剩余定理等。
    • 学习重点理解概念 + 掌握高效算法(如欧几里得算法、素数筛等),而非死磕数学奥赛中的证明题。
    • 学习资料:需要自行了解整除、素数等概念,因为有些算法书会从最大公约数开始讲。有了基本概念后,后边的数学内容都可以在算法书上学习。不建议看数论的奥赛书,因为侧重点不一样,基本只有概念推荐看,题目风格不一样所以也不用做,并且数学奥赛的数论书上一般也不会讲快速幂、素数筛等算法,总的来说看这种书只能学到一点点有用的东西。
  • 组合数学:包括加法/乘法原理、排列组合、组合数计算、二项式定理、容斥原理、鸽笼原理、常见计数方法和模型(例如捆绑法、插空法、隔板法、球盒模型、卡特兰数、斯特林数等)。
    • 学习重点:这是最值得系统学习的部分,并且个人觉得也是最能开发孩子思维能力的部分,强推结合高中教材和典型例题深入理解,个人觉得高中课内在这块讲得足够基础和系统。如果孩子小但非要学一些数学的话,我很建议从组合数学入手。
    • 学习资料:初期完全不了解相关概念和基本套路时可以参考高中数学教材,并建议做教辅上的一些数学题,有一定的了解后可以用算法竞赛书学习,更进阶的内容可以阅读《具体数学》这本书,里面有很多在信息学奥赛的组合问题中实用的推导和计算技巧。

T2级别:高阶内容(对 J 组绝大部分超纲,S 组中会出现但也不多)

  • 概率与期望:重在理解二者的概念和“期望的线性性”这一核心性质,比赛时通常会结合组合数学或动态规划考察。
  • 线性代数基础:矩阵、高斯消元法,用于求解线性方程组,数学上要做的就是搞懂原理,能写出模板代码。
  • 计算几何基础:主要是高中解析几何里圆和直线的知识,以及平面向量,比如直线/圆的方程(包括参数方程),直线与直线/直线与圆/圆与圆之间的位置关系等,J 组的话可能会用到初中平面几何的知识(比如咋判定一个四边形是矩形/菱形这种)。S 组中几乎不会考纯几何题,但是可能会出以几何作为背景的计数题。

三、各阶段数学知识学习路线图与资源建议

请注意,此处只列出了数学知识的的学习路线

不同阶段的学生,目标和学习重点各不相同。请根据您孩子的实际情况,参考以下路线图:

第一阶段:小学/初一 CSP-J 开始前

  • 核心目标:培养兴趣,建立基础思维,补充基本的数学知识,在年龄达标后争取在 CSP-J 中获得省一。
  • 重点学习内容
    • 全面掌握T0级别知识:时空复杂度、二进制与位运算、集合论基础、基本初等函数、数列与递推。
    • 初步接触T1级别知识:学习基础数论概念(如整除、gcd、素因数分解)和组合数学思想(加法/乘法原理、枚举计数)。
  • 方法与资源
    1. 紧跟校内数学:扎实学好课内的函数、集合等知识,这是一切的基础。
    2. 精读算法入门书:通过《深入浅出程序设计竞赛基础篇》等教材,掌握复杂度、位运算等编程专属概念,以及学习整除理论、基础排列组合的内容。
    3. 在编程中理解数学:通过编程实践(如完成深基的例题和课后题)来理解数学概念和算法,做到知行合一。

第二阶段:初一 CSP-J 结束后到初二赛前

  • 核心目标:系统学习 S 组以内的数学知识,冲刺 CSP-S 省级一等奖。
  • 重点学习内容
    • 攻克全部T1级别知识:系统学习上述提到的数论、组合数学的相关内容,并能够解决具有一定难度的问题。
    • 了解T2级别知识:对概率期望、线性代数、计算几何等建立基本概念,能使用这些算法解决比较直接的问题。
  • 方法与资源
    1. 知识点学习:使用《深入浅出程序设计竞赛进阶篇》、《算法竞赛进阶指南》、《算法竞赛》等书籍,配合优质博客学习相关知识。
    2. 高强度练习:在各大 OJ 找相关专题题单,进行集中学习和训练,要求 T1 级别知识的题单中蓝题(提高+/省选-)以及以下难度的题目都尽量做掉,积累题目中常用的数学推导和变形技巧,培养直觉。

第三阶段:初三与高一

  • 核心目标:NOIP 省一退役或冲刺省队。
  • 重点学习内容
    • 目标 NOIP 省一退役的选手:需要训练 T1 T2 级别的数学知识的蓝题以内难度的题目,并适当做一部分紫题(省选/NOI-),无需学习任何更高深的知识。
    • 目标冲刺省队的选手:在 T1 T2 级别的数学知识的练习不落下的基础上,为了应对省选,你还应该学习如多项式、生成函数、更深入的数论和组合理论、更复杂的计算几何算法,并做相应的题单去练习。
  • 方法与资源
    • 此时学生已具备一定的自学能力,可以通过《算法竞赛》学习高级数学算法知识点,研读《具体数学》等书籍提升数学素养,并挑战更高难度的题目。

四、给家长和学员的行动指南

  1. 定位:明确孩子当前的学段和目标赛事。
  2. 对标:参考上方的学习路线图,找到当前最需要优先掌握的知识点。
  3. 资源
    • 校内同步是基石:切勿为了竞赛完全抛弃课内数学,除非确实已提前系统掌握了课内的数学。
    • 善用网络资源OI Wiki 内容全但不精,当作目录或字典去使用;具体学习时去洛谷查洛谷日报的博客(一般来说质量比较高)或其他优质博客;学完之后利用各大 OJ 去练习对应的题目。
    • 选择一本好书:《深入浅出程序设计竞赛》基础/进阶篇、《算法竞赛》、《算法竞赛进阶指南》,都是很好的书籍,学习新知识点时可以书和优质博客结合去看。
  4. 心态:牢记 “应用优先” 原则。学习数学知识的目的,是为了解决编程问题。切忌脱离编程实践,陷入纯数学理论的海洋。

总结一下:

信息学竞赛中的数学学习,关键在于 “精准”“应用” 。希望这份指南能帮助您绕过弯路,高效地将数学能力转化为竞赛场上的竞争优势。

CSP-S 第二轮仅剩不到 20 天,弱省/中等省份如何冲刺省一

本文主要针对 CSP-S 一等奖分数线在 120-170 分波动的省份。

CSP-J/S 第二轮将于 11 月 1 号开考。在剩余的不到 20 天时间里,假如您这次的目标是稳稳拿到本省的 CSP-S 一等奖,但不追求高分,作为前算法竞赛选手以及现役教练,我将根据自己多年来的做题和比赛经验,给大家介绍一下这段时间如何针对性的进行准备,把自己会的知识和技巧稳稳地转化为分数。

首先,我想谈一谈 CSP-S 中的一道题目的分数是如何构成的

一般来说,提高级一道题目的分数构成如下:

  • 初级暴力分:阶乘复杂度或指数复杂度的枚举就能拿到的分数,T1 中这类分数可能能高达 30 分,题目位置越靠后的题这档分数就会越少。
  • 特殊性质分:例如输入的图是一棵树、树退化成一条链、序列中的数据两两不同、输入只包含题目中提到的情况的一个子集等,可设计针对性的算法得分。但注意,特殊性质分的难度差异很大,有的只要一两分钟就可以想出做法,有的苦思冥想都不知道特殊性质如何利用。
  • 中高档暴力分:经过简单分析后,发现可以以较高的多项式复杂度(比如 O(n4)O(n^4))解决掉的分数。如果进一步分析题目特点,发现可以使用数据结构或一些算法优化,有可能在此基础上得到更低一级复杂度的做法(例如降低到 O(n3)O(n^3)O(n2logn)O(n^2\log n)),从而得到更多的分数。
  • 正解分:能通过 100% 数据的做法才能拿到的分数。

然后,我们要知道,想拿 CSP-S 的一等奖,需要拿到哪些分数

  • 通过研究 CSP-S 以及 NOIP 真题,大家会发现第一题的正解考察的几乎总是 J 组知识点。如果你能完全解决掉第一题,那么后边 3 道题拿到最低档暴力分,就足够在弱省拿到省一了。
  • 如果您在中等省份,则除了最低档的暴力分之外,您还应该拿到至少两道题的性质分中档暴力分
  • 遇到 T1 较难的年份,您的目标是在 T1 拿到 60-70% 的分数,后边其他题靠初级暴力和特殊性质,最终一道题都不 AC 都可以拿到省一。

那么,为了拿到这些分数,我们应该具备怎样的水平,以及做哪些训练呢?

  • 通过研究 19 年以来的真题,我们可以发现:

    • T1 的正解的平均难度黄题简单些的绿题附近,虽然前几年有出现过蓝题 T1,但最近两年的 T1 都比较简单,属于橙题
    • T2、T3 的初级暴力分的难度为橙或黄(这里我们讨论的只是这一档分数的做法的难度,而不是整道题的难度,下同)。
    • T2、T3 特殊性质分的难度一般在黄或绿。
    • T2 中档暴力分一般是简单点的绿题的难度。
    • 上面提到的这些分数,用到的知识点绝大部分是 J 组知识点,例如二分、贪心、双指针、堆、简单 DP、DFS、BFS。
  • 因此,假如您的目标是中等或弱省 S 组压线一等奖,那么您的训练应该专注于做黄题绿题,并且应该是 J 组知识点的黄题和绿题

  • 在训练时,每道题目都应该按照比赛的要求,只能提交一次,第一次测出来是多少分就是你的成绩,这要求你在提交前进行充分的测试,包括但不限于测试题目给的大样例、手造边界情况数据、写暴力对拍。

  • 对于思维难度更高的题目或 S 组中比较复杂的知识点的题目,您近期应该多关注部分分如何去打,只要拿到了你想要的部分分,就算这个题做完了。如果你平时的能力只能场上做出一部分绿题,这个时候去研究蓝题或者更高难度的正解的收益不高。根据我的参赛和教学经验,一个人临时学到的技巧在考场上几乎不可能应用出来,并且还可能因为新学的技巧而导致考场上思维被带偏,或误认为自己能做出来某题,结果导致本来该拿的分数拿不到

  • 不要在这 20 天的时间里参与和 CSP-S/NOIP 差距比较大的比赛,例如 Codeforces、AtCoder、牛客等。OI 赛场上的比赛策略也不太适用于这类比赛,打这些比赛会打乱你平时练的节奏,很容易打崩,完了之后还会影响自己的信心。要打,就去打 OI 赛制的模拟赛。

考前这些天,你需要去记录近期训练和模拟赛时自己的失误应该复习的旧知识和技巧学到的新东西,下面我会放两个例子,展示一下具体怎么做这个事情:

手把手带的一个学生,按照我的要求,总结的近期失误表格:

题目链接 难度 标签 题解 学到的东西
T673603 绿 逆序对、树状数组、贡献法 题解+std 邻项交换次数逆序对关联很大,所以首先要想到这个作为切入点,后边的套路是容易的。
P7113 拓扑排序、__int128_t 题解 学到了怎么用 __int128_t。需要注意 __int128_t 需要模拟逐位输出,不要在关闭流同步时混用输出方式(例如 putcharcout 混用)。
P2536 绿 DP 题解 注意 DP 初始化,本题中所有的 dp[i][0]dp[0][j] 都需要初始化
T676142 绿 DP、组合数学 题解+std 赛时对拍都过了,思路也很流畅,最后挂了 30 分,原因是有个地方取模没取上,而对拍只拍了小数据,只测小数据是测不出溢出的。
CF105485I ~1900 数论、线段树 题解 gcd=0\gcd = 0 时,答案应该输出 nn,这个点被我疏忽了,之后通过对拍找到了 bug,或通过思考特殊情况也可以找到该问题。

学生在国庆时打的模拟赛的实况:

学生打完后总结的经验教训:

写了对拍但还是挂分了?CSP赛时测试方法总结

0. 前言

在信息学竞赛中,对拍是一种通过自动化比较两个程序在相同随机输入下的输出结果,来检验目标程序正确性的方法。该方法可以有效地发现很多通过手动测试难以找到的隐藏错误,是信奥赛这种 OI 赛制的比赛时必会的保命技能

相信大家从网上或者自己的信奥教练口中,都或多或少了解过对拍的重要性。有些选手在尝到了对拍的甜头之后,会错误的认为“对拍过了一定没问题”,而实际上对拍并不是万能的,仍然有一部分错误是对拍难以发现的。

在讲干货之前,先讲一个故事。我亲手带的一位学生,在国庆时报名参与了洛谷的国庆集训,在第二场模拟赛中,他想出了 T1 的正解,并且把写的正解和暴力做了对拍,场上对拍没有问题。但是,当正式出分时,我们发现他的 T1 只得了 70 分。出分后他非常的疑惑,跟我进行了一些交流,并把代码发给我看。这是一道求集合方案数的问题,答案很大需要取模输出。经过简单的分析,我发现他虽然最终的结果取模了,但在计算过程中的某个隐蔽的位置忘记了取模,这可能会导致计算过程中溢出。果然,改了这个地方之后他 AC 了这道题。本来他这场比赛能打 160 多分,但因为这个失误挂成了 130 多分。

这种“对拍 AC 提交挂分” 的情况其实是普遍存在的。当我还是竞赛选手时,我自己便亲身经历过三五次。但在最近几个月的教学中,我的学生还是第一次遇到。于是,我写了这篇文章,来盘点一下那些 “对拍也防不住的坑” ,并给大家提供一套赛时测试的 “组合拳” ,最大限度确保你能拿到的分数稳稳到手。

1. 对拍为何会“失灵”?—— 四大隐形杀手**

根据我的实战经验,对拍失效通常源于以下四类错误:

1. 对拍数据范围过小,测不出数据大时会触发的 bug

  • 场景:由于对拍时总有一个程序是暴力程序,而暴力程序本身只能处理比较小的数据范围(大了跑不出结果),所以对拍时数据生成器(gen.cpp)一般来说生成的数据也会比较小(如 n20n \le 20)。但赛时数据范围(如 n2×105n \le 2\times 10^5)会导致:
    • 整数溢出:计算中间值时少取模了一次,导致中间结果超过 int 甚至 long long 范围,即使最终答案取模也无济于事。
    • 数组越界:数组开小,小数据不越界,大数据随机访问到非法内存。经常做题的人都知道,平常做题时最常出现的数据范围是 2×1052 \times 10^5,因此很多人每做一道新题时会随手开这么大的数组。但部分题目的数据范围可能高达 5×1055 \times 10^5 甚至 10610^6,这个时候就会出现越界。
  • 案例:上面提到的学生,就是因为计算过程溢出,小数据没测出来。

2. 初始化与多组数据遗忘

  • 场景:题目说“包含多组测试数据”,但对拍时为了方便 debug,生成的数据都是 T=1T = 1 的情况。正式评测时,第二组数据会沿用第一组数据的全局变量值,可能导致答案错误。
  • 案例vector 或全局数组没有在每组数据前执行 clear()memset

3. 边界与特判疏忽

  • 场景:数据生成器很难覆盖所有边界情况,你可能会说 n=0n=0n=1n=1 这类情况多拍一会儿还是可以拍到的,但很多题目的边界并非是这样一种简单的情况,这些角落里的“魔鬼”总能精准地绕过你的对拍测试。
  • 案例:图论题中,一个经常出现的边界情况是树退化成一条链,这时候可能会把复杂度卡爆。当 n=20n = 20 时,随机生成的树是一条链的概率已经不足万分之一,更别说更大的时候了。这种数据几乎只能人工构造才能得到。

4. 复杂度误判

  • 场景:你以为你写的“正解程序”的复杂度是 O(nlogn)O(n \log n),但其实复杂度是 O(n2)O(n^2),你对拍时用 n=1000n=1000 的数据很快,但赛时大数据会让你程序超时(TLE)。
  • 案例set 的二分查找,如果使用 lower_bound(set.begin(), set.end(), key),则单次查找的复杂度为 O(n)O(n),只有使用 set.lower_bound(key) 时,单次查找的复杂度才是 O(logn)O(\log n)。多组数据测试时,使用了错误的清空方式,导致每次光清空数组的复杂度就拉满了。

2. 赛时测试“组合拳”:对拍是绝招,但不是唯一的招数

要想真正“保命”,你的测试应该至少包含以下几道防线:

第一招:测样例。除了题目 PDF 中明确给出的小样例,还可能会有以附件形式给出的大样例。有时候你写的只是一个暴力解法,所以测不了最大的样例,这是正常的,你只需要把你的程序能处理的大样例测一遍就好了。

第二招:测边界数据。首先你应该重新读题,发现题目中的边界情况,边界情况常常可以从题面的数据范围部分挖掘出,另外题面中或许会有加粗的描述,这可能也是边界情况。对于你发掘出的边界情况,每种至少手动构造 1 组数据测试。

第三招:对拍。对拍的前提是你这个题写出了两种解法,倘若你只会写这个题最暴力的做法,那么是无法对拍的。假如你一道题会写暴力和更高档的一个部分分,你就可以去对拍。对拍时,最核心的是你的数据生成程序。请确保你的数据生成程序中,每个数据的生成范围要完全符合题目的要求,比如不要题目说是非负数但你只生成正数。在第一次对拍前,请把你的数据生成程序的数据范围尽可能拉大,假如对拍测出 bug 了,再调小数据生成范围,以得到一个方便 debug 的输入

第四招:静态查错。在上面的测试都通过后,请强制自己做 3 分钟的代码审查,通读一遍代码,并思考下面几个问题:

  1. 数组大小:是否开小了(RE)?是否开得太多超过了内存限制(MLE)?
  2. 多组数据:全局变量和容器是否清空了?
  3. 数据类型:该用 long long 的地方是否都用了?1LL * a * b了吗?该取模的地方都取到了吗?
  4. 边界情况:n=0,1 时,程序会崩溃或输出错误吗?
  5. 文件读写freopen文件名是否正确?提交前是否注释掉?
  6. 调试语句:调试代码时的输出删掉了吗?

3. 总结:给你的赛时测试流程建议

最后,送你一个简单的 “提交前5分钟” 检查流程:

  1. 运行对拍:最大数据跑一组。
  2. 通过后:进行 “静态查错” ,逐条核对清单。
  3. 最终验证重新运行程序,输入样例数据,肉眼确认输出是否完全正确
  4. 提交:祈祷并相信自己的准备。

记住,对拍是你最强大的武器,但绝不是你唯一的武器。 只有将 “自动化对拍” 和**“边界情况测试”**以及 “人工静态审查” 结合起来,才能最大程度地避免非技术性失分,让你辛苦想出的算法换来应得的分数。

希望这篇文章能帮你在下次比赛中少踩坑,多得分!

模拟赛打到崩溃却没有提高?教练揭秘两大误区,让模拟赛效率翻倍!

模拟赛的意义:

  • 暴露问题,比如考试环境不熟悉、文件读写忘了写、比赛策略有问题。模拟赛的前期,暴露很多问题是很正常的,甚至是好事儿,因为你近期犯过这样的错误,吃过这样的亏,过几天正式比赛就不太会再栽一次相同的跟头。
  • 训练正确的答题节奏。

模拟赛打到崩溃却没有提高?教练揭秘两大误区,让模拟赛效率翻倍!

CSP/NOIP 冲刺期,每天泡在模拟赛里,分数却像进了冷冻层?

打一场累半死,赛后只想躺平,结果下一场还是老样子…… 如果你也感觉陷入了这种「无效循环」,那么今天这篇笔记就是为你写的。

作为带过不少学生的教练,我必须说:低效的根源,往往不在“打”的过程,而在“打”完之后的收尾工作。 避开这两个大坑,你的每一场模拟赛才能真正成为提分利器。

(长文预警,请在图片中查看)

CSP-J/S 和 NOIP 即将到来,相信各位选手们最近正在狂打模拟赛。一场比赛连续紧绷 4 小时会让人感到燃尽,赛后学生难免颓废懈怠一段时间,尤其是没打好的时候,这是人之常情。我们希望每次模拟赛后能够收获一些东西,可很多同学模拟赛刷了一套又一套,没有做好比赛的善后工作,导致打了很多比赛但分数却没有长进,这样的训练就很低效了。

作为带过不少学生的教练,我必须说:低效的根源,往往不在“打”的过程,而在“打”完之后的收尾工作。 避开这两个大坑,你的每一场模拟赛才能真正成为提分利器。

限于篇幅,这篇笔记会着重讲模拟赛后的两大误区以及正确食用姿势:

误区1:赛后不补题或瞎补题

不补题,顾名思义,就是打完就不再看这个比赛了,或听讲解或看题解懂了后却不动手实践。如果是下午考完的,当天累的不想补题很正常,但如果比赛安排得非常密集,第二天也没补题的话,后边又是新的模拟赛,这样滚雪球下去可能会直接不补了。

瞎补题,是补远超自身实际水平的题目超纲知识点题目,比如你的水平场切绿题(普及+/提高)的把握都只有五六成,现在去补紫题(省选/NOI-)的 AC 做法,就算补掉了,其需要的思维深度也不是这短短十几天能构建出来的,正式比赛考场上大概率也写不出来(有一种情况例外,就是思维难度可能只有蓝或更低,但实现有一定难度的题目,还是可以补的,提升代码能力总是对的)。你真想提升硬实力的话,在几个月前就应该去做这种难度的题了,或者等下个月比赛结束后再补。

那么,什么是有效补题?有效的补题是把赛场上不会/没来及写,但稍微跳一跳就能够到部分分或正解补掉,比如你是绿题(普及+/提高)的水平,就去补蓝题(提高+/省选-)以及更简单的题目的正解,紫题(省选/NOI-)的部分分。这些分数,你在赛场上都是有希望当场拿到的,你补了之后是能够基于你目前的水平,循序渐进搭建更深更广的思维链以及更强的代码能力。

误区2:赛后不复盘赛时发挥与策略

补题是好的,打一场比赛你终归是要有一点知识或能力上的提高的。但是你有没有想过,为啥这部分分数场上没拿到?以及,你拿到的分数,是没怎么折腾就轻松拿到的,还是思考良久或调试很久才拿到的?OI 赛制最后只打出一个分数,很多学生和家长只看最后分数如何,这样会丢失很多过程信息。

啥是过程信息呢?经常打 OI 的都知道,选手们每年正式赛之后总是喜欢写游记,而游记中的一部分就是记录赛时重要的时间点做的事情,例如采用了怎样的做题顺序,决定先写多少分的做法,遇到 bug 之后采用了什么方法调试,卡题时如何度过的难关,这些都是赛时的过程信息。

在训练阶段,如果忽视了选手拿这些分数的过程信息,会导致潜在的隐患不能被发现,比如某些题目死磕太久影响了做其他题目,或者比赛后期有多个分数可写但只能选一个写时,选的那个最后没有得分。

比如,同样是模拟赛打了 200 分,有人可能 2.5 小时就拿到了,尝试写后边的分数但未果,而有人折腾到最后才凑够。有人的分数是从前往后按顺序拿的,完全拿到一道题想拿的分数后再去做下一题,而有人是先写每道题的暴力分,再判断四道题中哪道题更好得分,从而决定后边的得分顺序。比赛后期有两个题 A 和 B 的部分分可写时,有人能力比较强,但保守策略先写简单的一个并拿到了分,但时间不够写另一个分数更高的了;有人富贵险中求,直接冲分数更高的那个,但是最后没写出来导致没有收益。

这些决策,大部分时候是有优化的空间的。多打几场比赛并对过程信息进行分析之后,每个选手都能知道自己在什么情况下适合做什么样的决策,我一般不会推荐任何一种决策,好策略是因人而异的,需要根据具体选手具体场次去具体分析。

每次模拟赛后,学生或许马上不想再做高精力消耗的事情,这个时候不妨趁着记忆比较清晰,去把自己场上的这些过程信息写下来,这个事情一般只需要花 10-20 分钟,相对比较轻松。根据我的观察,在让学生回忆完过程信息后,学生自己一般就会顺便对着这些过程信息思考,写一些经验教训,总结赛时做对以及做错了什么决策,如果学生不会分析,教练可以参与进去帮助学生复盘。这样总结过之后,即使学生真的懒得补题,但他下次的比赛策略大概率会比上一次成熟,这样就可以在不学新知识的情况下,可能拿到更多的分数。

总结

“好了,希望今天的分享对你有帮助。为了让大家能真正用起来,我为大家准备了一份 《模拟赛复盘自查清单》 的电子文档,里面列出了赛时、赛后需要关注的所有关键点。

获取方式很简单:只需要‘一键三连’本视频,并在这期视频的评论区留言‘需要复盘’,我会把下载链接私信给你。

也欢迎大家在评论区分享你打模拟赛时最难忘的一次决策失误或成功经验!”

部分分如何写以及如何对拍——以 CSP-S 2024 T2 为例

  • S 组一等,虽然新知识点无需多学了,但是有一个东西是必须要学的:对拍。并且我建议考前每道题目在提交前,都去写一个对拍去测,当成考试只有一次提交机会,交的第一次的得分就是你的成绩。假如你一道题写了不止一档分数的做法,就可以对其进行对拍,从而非常有效地检查出程序中潜在的问题。
  • 光对拍还是不够的,对拍一般来说是一个复杂度高的暴力做法和一个复杂度低的做法对拍,为了能在规定时间内跑出结果,我们不会生成很大的数据去测,必须保证暴力做法能够处理你的输入规模。但如果不测大数据,可能就测不出诸如数组开小、数值溢出、取模错误、复杂度实现错误等问题。
  • 对拍本身哪里容易写错?
  • 使用表格去记录近期自己的失误或学到的新东西。
  • 记录自己打模拟赛时的实况,用于复盘哪里可以改进。
  • 调整作息。
  • 少打乱七八糟的比赛,会影响比赛节奏,多打 OI 赛制的模拟赛。
  • 个性化精选每日一题,每天只针对性地多做一题,有效提升学习效果。

程序框架:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

// 原始输入的数据存储结构
LL n, m, L, V, p[N], d[N], v[N], a[N]; 

// 子任务 1 和 2 的做法
namespace subtask_1_to_2 {
    // 一些在本部分分中可能用到的数据结构
    bool too_fast[N]; 
    bool is_open[N];
    int max_close_cnt = 0; 
    
    // 真正的求解函数
    void solve() {
        
    }  
};

// 子任务 3 到 6 的做法
namespace subtask_3_to_6 {
    // 一些在本部分分中可能用到的数据结构
    bool too_fast[N]; 
    vector<int> open_pos;
    int max_close_cnt = 0; 
    
    // 真正的求解函数
    void solve() {
        
    }  
};

// 任务分发函数,读入数据后,判断数据具有什么特点,然后调用相应的求解函数
void dispatch() {
    cin >> n >> m >> L >> V;
    for (int i = 1; i <= n; i++) {
        cin >> d[i] >> v[i] >> a[i];
    } 
    for (int i = 1; i <= m; i++) {
        cin >> p[i]; 
    }
    
    if (n <= 20) {
     	subtask_1_to_2::solve();   
    } else if (所有的 a[i] >= 0) {
        subtask_3_to_6::solve();
    } else if (其他条件) {
        其他做法
    }
}

int main() {
    freopen("detect.in", "r", stdin);
    freopen("detect.out", "w", stdout);
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int T = 1;
    // 如果不是多组数据读入,就注释掉下一行
    cin >> T;
    while (T--) {
        dispatch();
    }
    
    return 0;
} 

subtask_1_to_2 的暴力代码:

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 2e5 + 10;

LL n, m, L, V, p[N], d[N], v[N], a[N]; 

namespace subtask_1_to_2 {
    bool too_fast[N]; 
    bool is_open[N];
    int max_close_cnt = 0; 
    
    void dfs(int u) {
        if (u == m + 1) {
            for (int i = 1; i <= n; i++) {
                if (too_fast[i]) {
                    bool found = false;
                    for (int j = 1; j <= m; j++) {
                        if (p[j] >= d[i] && is_open[j]) {
                            LL v2 = v[i] * v[i] + 2 * a[i] * (p[j] - d[i]);
                            if (v2 < 0) {
                                break;
                            }
                            if (v2 > V * V) {
                                found = true;
                                break;
                            }
                        }
                    }
                    if (!found) {
                        return;
                    }
                }
            }
            
            int cnt = 0;
            for (int i = 1; i <= m; i++) {
                if (!is_open[i]) {
                    cnt++;
                }
            } 
            
            max_close_cnt = max(max_close_cnt, cnt);
            
            return;
        } 
        
        dfs(u + 1);
        is_open[u] = false;
        dfs(u + 1);
        is_open[u] = true;
    } 
    
    void solve() {
        for (int i = 1; i <= n; i++) {
            too_fast[i] = false;
        }
        
        for (int i = 1; i <= n; i++) {
            bool ok = true;
            for (int j = 1; j <= m; j++) {
                if (p[j] >= d[i]) {
                    LL v2 = v[i] * v[i] + 2 * a[i] * (p[j] - d[i]);
                    if (v2 < 0) {
                        break;
                    } 
                    if (v2 > V * V) {
                        ok = false;
                        break; 
                    }
                }
            }
            if (!ok) {
                too_fast[i] = true;
            }
        } 
        
        int too_fast_cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (too_fast[i]) {
                too_fast_cnt++;
            }
        }
        cout << too_fast_cnt << " ";
        
        max_close_cnt = 0;
        for (int i = 1; i <= m; i++) {
            is_open[i] = true;
        }
        dfs(1); 
        
        cout << max_close_cnt << "\n";
    }  
};

void solve() {
    cin >> n >> m >> L >> V;
    for (int i = 1; i <= n; i++) {
        cin >> d[i] >> v[i] >> a[i];
    } 
    for (int i = 1; i <= m; i++) {
        cin >> p[i]; 
    }
    
    subtask_1_to_2::solve();
}

int main() {
    #ifdef LOCAL_FILE_IO
    freopen("detect.in", "r", stdin);
    freopen("detect.out", "w", stdout);
    #endif
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
    
    return 0;
} 

性质 A 和 B 的代码:

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 2e5 + 10;

LL n, m, L, V, p[N], d[N], v[N], a[N]; 

namespace subtask_3_to_6 {
    void solve() {
        int too_fast_cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (p[m] < d[i]) {
                continue;
            } 
            LL v2 = v[i] * v[i] + 2 * a[i] * (p[m] - d[i]);
            if (v2 > V * V) {
                too_fast_cnt++;
            }
        }
        
        cout << too_fast_cnt << " ";
        if (too_fast_cnt == 0) {
            cout << m << "\n"; 
        } else {
            cout << m - 1 << "\n";
        }
    }
};

void solve() {
    cin >> n >> m >> L >> V;
    for (int i = 1; i <= n; i++) {
        cin >> d[i] >> v[i] >> a[i];
    } 
    for (int i = 1; i <= m; i++) {
        cin >> p[i]; 
    }
    
    subtask_3_to_6::solve();
}

int main() {
    #ifdef LOCAL_FILE_IO
    freopen("detect.in", "r", stdin);
    freopen("detect.out", "w", stdout);
    #endif
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
    
    return 0;
} 

数据生成代码:

#include <bits/stdc++.h>

using namespace std;

int rd(int a, int b) {
    return rand() % (b - a + 1) + a;
}

int main() {
    srand(time(NULL));
    int T = rd(1, 3);
    cout << T << "\n";
    while (T--) {
        int n = rd(5, 10), m = rd(5, 10);
        int L = rd(1, 1000000), V = rd(1, 1000);
        cout << n << " " << m << " " << L << " " << V << "\n";  
        for (int i = 1; i <= n; i++) {
            int d = rd(1, L), v = rd(1, 1000), a = rd(0, 1000);
            if (rand() % 2) {
                a *= -1;
            }
            cout << d << " " << v << " " << a << "\n";  
        } 
        vector<int> p; 
        for (int i = 1; i <= m; i++) {
            p.push_back(rd(0, L)); 
        }
        sort(p.begin(), p.end());
        for (auto pos : p) {
            cout << pos << " ";
        }
        cout << "\n";
    } 
    return 0;
}

对拍检查代码:

#include <bits/stdc++.h>

using namespace std;

int main() {
    system("g++ detect.cpp -o wrong.exe -Wall -std=c++14 -O2 -static");
    system("g++ d.cpp -o correct.exe -Wall -std=c++14 -O2 -static");
    system("g++ gen.cpp -o gen.exe -Wall -std=c++14 -O2");
    while (true) {
        system("gen.exe > in.txt");
        system("wrong.exe < in.txt > wrong.out");
        system("correct.exe < in.txt > correct.out");
        if (system("fc wrong.out correct.out")) {
            system("pause");
            break;
        } 
    } 
    return 0;
}

小红书文案

标题:CSP-S最后20天冲省一?教练说破大实话!(弱/中等省必看)

正文

CSP-J/S 第二轮将于 11 月 1 号开考,时间紧,任务重!最后20天,方向比努力更重要

如果你在一等奖分数线120-170分的省份,且目标就是 “稳稳上岸” ,这篇笔记就是为你写的。作为前竞赛选手以及现在的竞赛教练,不讲空话,全部都是硬核分析与建议!

👇🏻下方长图,是我为冲刺学员总结的最后 20 天冲刺攻略,包含:

  1. 💡 分数拆解真相:CSP-S每道题的分数是怎么构成的?告诉你哪些分是必须拿到手的,哪些分可以战略性放弃
  2. 🎯 能力精准定位:想压线省一,到底需要具备什么水平?你需要专注练习哪种难度哪种知识点的题目?(答案可能比你想象的简单!)
  3. 🛠️ 学员实战案例:展示真实学员的复盘表格模拟赛实况记录,看他们如何通过“精细化复盘”实现提分。
  4. ❗ 核心避坑指南:为什么赛前不要乱打CF?为什么不能死磕难题?考前最影响心态的几件事,帮你一一避坑。

本文价值:帮你把有限的备考时间,最大化地转化为考场分数。拒绝无效努力,冲刺省一,看这一篇就够了!

➡️ 赶紧收藏点赞,把图片存好,对照执行吧!

赞赏