专栏文章

【欢迎投稿】一文概括所有比赛注意事项,以及同类资料推荐

算法·理论参与者 10已保存评论 9

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
9 条
当前快照
1 份
快照标识符
@mhz4v0ox
此快照首次捕获于
2025/11/15 01:29
3 个月前
此快照最后确认于
2025/12/02 01:44
3 个月前
查看原文

大部分文章在我的 LibreOJ更新,其他平台可能更新不及时,但你仍可以在其他平台投稿征集内容和建议。

个人著作权声明:严禁任何未经本人(刘承奥,常用笔名/网名:蔡德仁 CommonAnts LCA liu_cheng_ao)书面授权者在梦熊联盟,或者任何虚假宣传或不实营销炒作或不正当竞争行为严重的 OI 机构的课程内或交流平台(包括但不限于品牌集训线下讨论,交流群,OJ,公众号,视频号等)上引用、传播、讨论此内容,以及本人于2024年5月及之后发布的所有内容,包括声明为公开的内容在内。

使用这些建议时请确保你理解其内容,由于使用这些建议造成的问题与本文作者无关。

如果你有自己熟悉的代码习惯,考前不要变更自己的代码习惯!这个优先级高于一切!可以考完后花一个月慢慢适应好的写法。本文也不是唯一答案,如果你习惯你自己的策略,那就不要改,参考一下细节即可。

省流版

包含下述所有内容的摘要:
  • 在平时模拟赛就学会并习惯比赛策略,命令行用法,对拍等内容!
  • 考试须知 身份证,准考证,别迟到,收手机,考场纪律啥的。请看:官方提供的考试须知。
  • 比赛策略
    • 前 15% 时间读所有题并想所有题,然后开始写最高的分以及调试,最后 5 分钟检查
    • 看题一定要逐词看过去,包括封面和提示。读完题先手算小样例确认理解。
    • 先看完所有东西,再想过所有东西,最后写东西。
    • 每几分钟(或喝水,伸懒腰)提醒自己是否发呆,发呆立刻切换
    • 先写得分性价比高的,性价比相近时先写短的(比如暴力)。
    • 先写程序框架,每段大致含义,关键内容的原问题精确数学意义,后填代码。
    • 有时间要对拍
    • 要写部分分
    • 别怀疑别人比自己强/自己是弱校弱省/做不出题怎么办,至少你看的这个指南是最强的老师写的不是吗?
  • 编程注意事项
    • 与其去背易错细节害怕记漏,不如养成好习惯消除错误风险!
    • 缺头文件 问:头文件打少了爆零怎么办?
      • 答:#include <bits/stdc++.h>
    • 命名冲突 问:自建变量 y1 next pipe 等和标准库重名爆零怎么办?
      • 答:namespace 包裹自己的程序
    • 少用宏 问:#define 让我爆零怎么办?
      • 答:非必要不用宏,用 using 和函数等代替。
    • 编译器帮你查错 问:函数没返回值/隐式类型转换出错/重名变量等查不出怎么办?
      • 答:添加编译选项 -Wall -Wextra -Wconversion -Wshadow 让编译器帮你检查。
    • 调试工具 问:有什么工具帮我查运行错误/下标越界/数值溢出?
      • 答:调试时按需求添加编译选项 -g3 -fsanitize=address,undefined -D_GLIBCXX_DEBUG 等并使用对应的 gdb asan 等工具。
    • 忘改调试/注释 问:调试的时候注释了 freopen 忘记恢复/忘删调试语句爆零?
      • 答:采用宏 #ifdef MYDEBUG,不要注释。
    • 输入输出优化 问:用什么输入输出?要读入优化吗?
      • 答:平时用关同步 cin/cout,量巨大时用 cin.read() cin.gcount()/cout.write() cout.flush()
    • 开栈空间 问:我本地测试怎么开大栈空间?
      • 答:Linux(含 NOILinux)在终端运行程序前先运行指令 ulimit -s 1000000,Windows 编译选项添加 -Wl,--stack=1000000000
    • 测时间和内存 问:如何测自己程序时间和内存?
      • 答:Linux 用 time ./程序名 测时间,ulimit -v <内存限制KB数> 测超内存限制。Windows 用程序内 clock() 测时间,用命令行 cmdsize 测内存。
    • 考试环境是 Windows 问:考试环境是 Windows 怎么办?还有前面没提到的吗?
      • 答:务必打开显示文件扩展名。
    • 不要写超标语法 问:用超标/被禁语法爆零了怎么办?
      • 答:平时就别写超过 -std=c++14#pragma 之类的违禁品。

防爆零指南之:考试须知和考前准备

  • 在平时模拟赛就学会并习惯比赛策略,命令行用法,对拍等内容! 临时抱佛脚可翻到本文最后一部分的推荐文章。
  • 身份证,准考证,别迟到,收手机,考场纪律啥的。请看:官方提供的考试须知,以及考试现场通知。
  • 试机一定要试机器,有问题找监考不要自己修电脑,监考水平差不要惊慌,不要顶撞。试机器特别要注意键盘鼠标功能,开关机不还原的区域等。Windows 的话还得注意找编译器路径考试的时候设置环境变量方便 cmd

防爆零指南之:比赛策略(如果你有自己熟悉的习惯和策略,那就不要改!)

要点:
  • 前 15% 时间读所有题并想所有题,然后开始写最高的分以及调试,最后 5 分钟检查。 如果你没有一个自己的非常明确的策略建议这个。检查包括关闭代码编辑器,用题面说的编译选项编译代码,测试样例输入输出正确(特别注意不能有调试输出),检查文件名、文件夹格式等,检查期间和之后不要打开代码文件以免误触。
  • 看题一定要逐词看过去,包括封面和提示。读完题先手算小样例确认理解。
  • 先看完所有东西,再想过所有东西,最后写东西。 不要先写第一题后看第三题。读题的时候除了你能立刻流畅想下去的做法继续想,一旦卡一点就记下来,不要强行想。先看完后面的题,对所有东西有个整体把握。同理,开局第一小时还有题没想过的情况下,会做题了也不要急着写,先记下程序框架和关键变量的意义,然后先想想其他题。
  • 每几分钟(或喝水,伸懒腰)提醒自己是否发呆,发呆立刻切换。(上厕所调整,换大思路,写其他题等)避免浪费时间。不要一直死磕想/调一个题。
  • 先写得分性价比高的,性价比相近时先写短的(比如暴力)。 得分性价比是脑内大致估测的得分除以预计写代码时间,别花时间估计,有个印象就行。
  • 先写程序框架,每段大致含义,关键内容的原问题精确数学意义,后填代码。 可以用注释写。内容含变量、数组、类型、函数等,要精确到 f[i]f[i] 到底是考虑了 i\le i 还是 <i\lt i 的状态。这样可以解决自以为会了但没想清楚的,调试的时候也可以逐段对比反例找到错点。
  • 有时间要对拍。 另外写的暴力也能帮你得部分分,打表找性质等。有时间就枚举所有小数据(能测边界情况)+测试极端数据。没时间对拍就手算几组小数据和小边界。
  • 要写部分分。 时间多又不会写更高分,且对拍写完时,尝试写乱搞。
详情请看课件:《如何打比赛.pptx》《编程基础:设计,测试,调试.pptx》。(我目前没地方放公开文件,暂且可以加我的QQ群 435253885。UOJ 群也发了如何打比赛。欢迎各大 OI 群(除了声明中的机构群)和各位老师同学转载。)

防爆零指南之:编程注意事项(如果你有自己熟悉的习惯和策略,那就不要改!)

与其去背易错细节害怕记漏,不如用好习惯消除错误风险!

缺头文件

问:头文件打少了爆零怎么办?
答:#include <bits/stdc++.h>
详情:竞赛官方使用的编译器 GNU-GCC-G++ 提供的 <bits/stdc++.h> 包含了 C++ 所有的常用标准头文件(除了竞赛不涉及的新版内容外)。打竞赛时养成用且只用 #include <bits/stdc++.h> 的习惯,平时打竞赛也不要用 <ext/pbds> <windows> <dev/random> 等非标准内容。特别注意头文件里的斜杠是正着 / 不是反着,否则 NOILinux 会编译错误!

命名冲突

问:自建变量 y1 next pipe 等和标准库重名爆零怎么办?
答:namespace 包裹自己的程序
CPP
#include<bits/stdc++.h>

using namespace std;

namespace my_namespace{
    // 我的程序
    int main(){
        // 我的程序
        return 0;
    }
};

int main(){ return my_namespace::main(); }
这样就会优先读你定义的名称,不会因为库函数重名错误。另外,不要用宏 #define 改名字

少用宏

问:#define 让我爆零怎么办?
答:非必要不用宏,用 using 和函数等代替。
宏是在代码文字层面的操作,会有各种意想不到的错误。
例如:
  • #define M(x,y) x=(x+y)%mod 然后 M(a,n>>2); 会变成 a=(a+n>>2)%mod;,然后 + 的运算优先级高于位运算,a=((a+n)/4)%mod;,爆零。
  • #define lowbit(x) ((x)&-(x)) 然后 lowbit(f(n)) 会导致函数 f(n) 被调用两遍,复杂度和副作用都爆了。
  • #define 和标准库冲突的名字,能穿透所有 namespace 直接让你爆零。
  • 忘记加 #undef 导致后面都错了。
除了 #ifdef 包裹调试语句之类的必要情况,或你非常熟练的模板,都不要用宏!即使是必要情况,也一定要加对应的 #endif #undef 等,并且尽量别用括号里有参数的宏!
危险程度:反复改代码 > 用宏 > 用其他语法!
常见替代方案:
  • #define int long long:用 using ll = long long; 代替(不要改 intll 之类的简写就行),以及代码编辑器的查找替换功能代替。
  • #define lowbit(x) ((x)&-(x)):用函数 int lowbit(int x){return x&-x;} 代替。现在都有 -O2,这不会变慢!
  • #define endl '\n',用了自己的命名空间后,你可以在 my_namespace 里定义 const char endl='\n';
  • 局部复用代码片段(例如写双指针复用指针移动操作):使用函数、lambda 表达式等代替。(平时不熟悉的不建议用 lambda)

编译器帮你查错

问:函数没返回值/隐式类型转换出错/重名变量等查不出怎么办?
答:添加编译选项 -Wall -Wextra -Wconversion -Wshadow 让编译器帮你检查。
  • -Wall 警告很多东西例如未使用的变量,未初始化的变量,%dlong long,if-else 嵌套不写大括号有歧义等。
  • -Wextra 额外警告一些东西。
  • -Wconversion 警告隐式类型转换的问题,比如 intunsigned int/size_t 比较会导致 -1 > 0u 乃至 vector a; for(int i=1;i<=a.size();i++); 死循环。
  • -Wshadow 警告局部和全局,内层循环和外层变量同名。
  • -fno-ms-extensions 关闭一些和微软编译器一致的特性,比如不标注返回值类型的函数。只有 Windows 需要,Linux 的默认就有。
  • 开多了会有很多警告,编译信息太长翻不完,建议给编译命令最后添加 2> log.txt 重定向编译信息。以免其实没错还在虚空调试。Linux 也可以在编译命令最后再加 && echo ok,这样编译成功包括无错误只有警告时也会最后输出 ok
除此之外还有 -Wformat=2 更严格检查格式化字符串(但建议直接用 cin/cout),-Wpedantic 检查非标准代码(比如 (Node){u,v,w}),等其它查错。这些 OI 没用,所以不推荐。

调试工具

问:有什么工具帮我查运行错误/下标越界/数值溢出?
答:按需求添加编译选项 -g3 -fsanitize=address,undefined -D_GLIBCXX_DEBUG 等并使用对应的 gdb asan 等工具。
注意这些会让程序变慢,测速不要加,而且不能和 -O2 一起加。
  • -g3对应 gdb 不赘述了,基础调试工具。不过,有时候直接输出调试更好;标准错误输出 stderr(比如 cerr<<x<<endl;)可以立即输出,多输出几个断点也能找到 RE 在哪。
  • -fsanitize=address,undefined 添加后可以检查一些数组越界和无符号溢出(取模题很有用)等,参见:Awesome sanitizers
  • -D_GLIBCXX_DEBUG 可以让一些库函数和 STL 多检测(例如开了之后 lower_bound 会先检查是不是排好序了)。这个检测可能会提高程序的时间复杂度,测大数据不要开。

忘改调试/注释

问:调试的时候注释了 freopen 忘记恢复/忘删调试语句爆零?
答:采用宏 #ifdef MYDEBUG,不要注释。
采用 #ifdef MYDEBUG #endif 包裹调试语句,采用 #ifndef MYDEBUG #endif 包裹调试要屏蔽的语句(比如 freopen)。调试时,给编译选项加上 -DMYDEBUG 即可。
另外:Windows/Linux 命令行都可以用 2> 把错误输出定向到文件。例如 Linux 下 ./aa < aa.in > aa.out 2> log.txt 可以从 aa.in 读数据,向 aa.out 写数据,把 cerr 都输出到 log.txt。特别地,2> NUL 忽略错误输出,> NUL 忽略输出。(程序本身没 freopen 对应的 stdin stdout stderr 才有用)Windows 也可以,除了 ./aa 改成 aa.exe

输入输出优化

问:用什么输入输出?要读入优化吗?
答:平时用关同步 cin/cout,量巨大时用 cin.read() cin.gcount()/cout.write() cout.flush()
注意:
  • 关同步。在 freopen 后面加上 ios::sync_with_stdio(false); cin.tie(nullptr);
  • '\n' 代替 endl 以免刷新输出缓冲区开销。
  • 需要记一下设置输出小数格式的语句 cout << fixed << setprecision(10) << endl; 从此开始保留固定 1010 位小数;cout << defaultfloat << setw(9) << a << endl; 从此开始保留 99 位有效数字。(这里省略了 std::
  • 奇奇怪怪的输出格式要求建议手写函数转成字符串输出。
  • 量巨大:一般 cin/cout 调用总次数(比如输入输出数字个数)超过 2×1062\times 10^6 次算巨大。输入输出单个长字符串开销不大。
建议不用 scanf/printf,现在效率一般不如关同步 cin/cout,且格式化字符串和取地址容易出错。调试也用 cerr 即可(嫌格式麻烦可以把调试输出语句单独抽出来写成函数)。
对于很大的输入输出,手写函数解析输入输出字符串,然后使用 cin.read() cin.gcount() 一次性输入大量内容,使用 cout.write() cout.flush() 一次性输出大量内容。(和 fread/fwrite 功能相同,但更可读)一般建议开一个 2162^{16} 字节的 char 数组做输入输出缓冲区(也就是一次性最多读写这么多内容),开小了会慢,开太大太占内存而且卡缓存。
注意手写之后就不能和普通 cin/cout 混用了。cerr 因为是独立的错误输出所以不受影响。
示例代码:
CPP
// 感谢UT和前人提供的基础代码。示例代码为了简短只有 int
#include <bits/stdc++.h>
using namespace std;
const int S = 1 << 16;
char buf[S], *p1, *p2, obuf[S], *O = obuf;
int getChar() {
  if (p1 == p2) {
    p2 = (p1 = buf) + cin.read(buf, S).gcount();
    if (p1 == p2) return EOF;
  }
  return *p1++;
}
int readInt() {
  bool f = false;
  char ch;
  while (!isdigit(ch = getChar())) f |= ch == '-';
  int x = ch & 0xf;
  while (isdigit(ch = getChar())) x = x * 10 + ch - '0';
  return f ? -x : x;
}
void putChar(char c) {
  if (O == obuf + S) cout.write(O = obuf, S);
  *O++ = c;
}
void printLine(int x, char c = '\n') {
  if (x < 0) putChar('-'), x = -x;
  if (!x)
    putChar('0');
  else {
    static char stk[21];
    int t = 0;
    while (x) stk[t++] = x % 10 | '0', x /= 10;
    while (t) putChar(stk[--t]);
  }
  putChar(c);
}
int main() {
  cin.tie(nullptr)->sync_with_stdio(false);  // cin 另一种关同步写法
  printLine(readInt());
  return cout.write(obuf, O - obuf).flush(), 0;
}

开栈空间

问:我本地测试怎么开大栈空间?
答:Linux(含 NOILinux)在终端运行程序前先运行指令 ulimit -s 1000000,Windows 编译选项添加 -Wl,--stack=1000000000
Linux 下 ulimit 可以调整终端包括它调用的程序的诸多限制,-s 1000000 表示把栈空间设为 106KiB10^6KiB(略低于 1GiB1GiB)。Windows 栈空间可以在编译时添加 -Wl,--stack=1000000000 设置为上限 10910^9 字节(略低于 1GiB1GiB)。当然栈空间就算取消限制还是算在总内存限制的。
不要设置为 unlimited,防止由于无限递归导致系统崩溃。
默认情况下,Windows 栈空间通常是 2MiB2MiB,Linux 通常是 8MiB8MiB。这样如果 dfs 太多层会超过栈空间上限运行时错误崩溃。好消息是 NOI 系列正式比赛都会开无限制栈空间,不超总内存限制就行。

测时间和内存

问:如何测自己程序时间和内存?
答:Linux 用 time ./程序名 测时间,ulimit -v <内存限制KB数> 测超内存限制。Windows 用程序内 clock() 测时间,用命令行 cmdsize 测内存。
Linux 编译好程序 aa 后用 time ./aa 测时间(要定向输入输出就 time ./aa < aa.in > aa.out),看的是几个值中的用户时间(user);用 ulimit -v 524288 限制内存到 512MiB512MiB,建议实际开 ulimit -v 500000 即可,留点空余。这个测试方法和 NOI 规则相同(看的是申请空间而不是实际使用的页数)。
想改变 ulimit -v 内存限制请关掉终端重开。 同一个终端 ulimit 开内存只能从大往小开,不能反着来。
Windows 在程序里添加 cerr << clock() << endl; 来输出运行时间(建议也和其他调试一样用 #ifdef MYDEBUG #endif 包裹)。用命令行 cmdsize aa.exe 命令测内存。
此处没有推荐难记的做法比如 std::chrono,会的可以用。
注意手动输入数据也算时间,测时间请开 freopen 或者用 < aa.in > aa.out 定向输入输出文件。

考试环境是 Windows

问:考试环境是 Windows 怎么办?还有前面没提到的吗?
答:务必打开显示文件扩展名。
你可以本文内搜索 Windows 这个词。
  • 如果你使用 Windows 系统,请务必把文件查看中的“显示文件扩展名”打开,不然你命名为 a.cpp 实际上是 a.cpp.cpp 直接爆零。

不要写超标语法

问:用超标/被禁语法爆零了怎么办?
答:平时就别写超过 -std=c++14#pragma 之类的违禁品。
至于有啥?自己开编译器 -std=c++14 测!NOILinux 默认这个不用开。

样例

  • 小明参加 NOIP。
  • 小明详细读完了四个题并手算了每个题最小样例,期间看完第一题直接顺利想出了做法,这些一共花了 15 分钟。题目的输入输出文件名是 aa problemb cti di。(现在是 15 分钟)
  • 小明想了想第二题,10 分钟想出了 60 分做法,满分暂时不会。他又去想第三题,10 分钟想出了第三题做法是 DP,但是不好写,好写的暴力是 30 分其它部分分也不好写,于是又花 10 分钟想清楚写下几个主要函数的外壳和关键的状态数组在原问题的准确意义。剩下第四题看了 5 分钟啥也不会,只能写 20 分暴力,如果用数据结构维护可以到 45 分,但问题本身就复杂要写很久。(现在是 50 分钟,小明会上限 305 分,其中 210 分好写)
  • 小明 20 分钟写完第一题并通过所有大小样例,手造的 1 0 边界数据通过,极限数据开查错编译/调试选项测试报了个越界,发现一个数组下标开错了,花 10 分钟。(现在是 1 小时 20 分钟,写了 100 分)
  • 小明 30 分钟写完第二题 60 分并通过小样例,特殊性质大样例没过,查错编译/调试选项报了个 int 溢出,发现是有个地方取模漏了。10 分钟解决通过特殊大样例。(现在是 2 小时,写了 160 分)
  • 小明再想想第二题满分怎么做,但几分钟无果,上了个卫生间放松还是没思路,决定继续写后面的。(现在是 2 小时 10 分钟)
  • 小明觉得第三题难写,决定先写第四题暴力。15 分钟写了 20 分通过小样例。(现在是 2 小时 25 分钟,写了 180 分)
  • 小明利用代码框架在 45 分钟内写出了第三题,但过不了小样例。开查错编译/调试选项测试没错。先测了手造的 1 1 边界数据过不了,花了 5 分钟修好,但还是过不了小样例。考虑到其他题都写过了,小明决定开始对拍调试。(现在是 3 小时 15 分)
  • 小明 10 分钟写完了小暴力和数据生成器并测试暴力过了小样例。(现在是 3 小时 25 分)
  • 小明开始从小到大对拍数据,在 n=4 拍出错了,他把权值手动改小找到了更简单的错误数据。然后小明在代码中间用 #ifdef MYDEBUG cerr #endif 加了调试输出中间结果,并用 #ifndef MYDEBUG #endif 框住了 freopen,重新开查错编译/调试选项编译时添加了 -DMYDEBUG,方便调试时读其他文件 ./cti < cti.in > test.out 2> cerr.log。他运行小数据并手算中间状态的数学意义该有的值,找到了几个错误。反复几次后他暴力能跑的范围小数据对拍上了,并且极限数据也没报错。这些调试用了 30 分钟。(现在是 3 小时 55 分,写了 280 分)
  • 现在小明时间不多了,决定再去卫生间冷静一下。突然他想到利用反悔贪心的思路能解决第二题,并且应该好写。他尝试脑内调整法大概验证了下正确性证明思路。(现在是 4 小时)
  • 小明把原来的第二题代码备份了一下,然后开始写第二题满分做法。但这时候电脑卡死了,小明举手示意监考,监考无法解决只能重启,给小明加时 5 分钟。(现在是 4 小时 5 分)
  • 幸运的是,因为贪心代码很简单,他 20 分钟就写完并且过了大小样例,开查错编译/调试选项也没报错。(现在是 4 小时 25 分,写了 320 分)
  • 小明最后 5 分钟检查程序,先关闭代码文件,检查了文件夹路径和文件名,然后复制题面PDF第一页的编译选项,没加自己的选项编译通过了四个题,并且运行了小样例,肉眼和 diff 分别验证了小样例正确性。然后关闭了所有命令行和窗口,考试结束了。
  • 加时的 5 分钟小明又检查了一次文件夹,文件名和编译通过。
  • 成绩出来了,小明第二题和第三题还是挂了 3 个特殊边界数据点,最后总分 305305
  • 小明感叹一声:还好不是 CSP-S,不然只有 4 个小时时间,自己恐怕没时间做第二题了。

防爆零指南之:其他内容推荐和琐碎细节合集

看太多会记不过来,而且其中重要内容在前面都有了。没时间不用看。
如果你想看:

部分我查过的其它资料列表(不要通读它们,本文足够)

我的个人链接(厚颜无耻)

评论

9 条评论,欢迎与作者交流。

正在加载评论...