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

· · 算法·理论

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

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

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

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

省流版

包含下述所有内容的摘要:

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

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

要点:

详情请看课件:《如何打比赛.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 包裹自己的程序

#include<bits/stdc++.h>

using namespace std;

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

int main(){ return my_namespace::main(); }

这样就会优先读你定义的名称,不会因为库函数重名错误。另外,不要用宏 #define 改名字

少用宏

问:#define 让我爆零怎么办?

答:非必要不用宏,用 using 和函数等代替。

宏是在代码文字层面的操作,会有各种意想不到的错误。

例如:

除了 #ifdef 包裹调试语句之类的必要情况,或你非常熟练的模板,都不要用宏!即使是必要情况,也一定要加对应的 #endif #undef 等,并且尽量别用括号里有参数的宏!

危险程度:反复改代码 > 用宏 > 用其他语法!

常见替代方案:

编译器帮你查错

问:函数没返回值/隐式类型转换出错/重名变量等查不出怎么办?

答:添加编译选项 -Wall -Wextra -Wconversion -Wshadow 让编译器帮你检查。

除此之外还有 -Wformat=2 更严格检查格式化字符串(但建议直接用 cin/cout),-Wpedantic 检查非标准代码(比如 (Node){u,v,w}),等其它查错。这些 OI 没用,所以不推荐。

调试工具

问:有什么工具帮我查运行错误/下标越界/数值溢出?

答:按需求添加编译选项 -g3 -fsanitize=address,undefined -D_GLIBCXX_DEBUG 等并使用对应的 gdb asan 等工具。

注意这些会让程序变慢,测速不要加,而且不能和 -O2 一起加。

忘改调试/注释

问:调试的时候注释了 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()

注意:

建议不用 scanf/printf,现在效率一般不如关同步 cin/cout,且格式化字符串和取地址容易出错。调试也用 cerr 即可(嫌格式麻烦可以把调试输出语句单独抽出来写成函数)。

对于很大的输入输出,手写函数解析输入输出字符串,然后使用 cin.read() cin.gcount() 一次性输入大量内容,使用 cout.write() cout.flush() 一次性输出大量内容。(和 fread/fwrite 功能相同,但更可读)一般建议开一个 2^{16} 字节的 char 数组做输入输出缓冲区(也就是一次性最多读写这么多内容),开小了会慢,开太大太占内存而且卡缓存。

注意手写之后就不能和普通 cin/cout 混用了。cerr 因为是独立的错误输出所以不受影响。

示例代码:

// 感谢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 表示把栈空间设为 10^6KiB(略低于 1GiB)。Windows 栈空间可以在编译时添加 -Wl,--stack=1000000000 设置为上限 10^9 字节(略低于 1GiB)。当然栈空间就算取消限制还是算在总内存限制的。

不要设置为 unlimited,防止由于无限递归导致系统崩溃。

默认情况下,Windows 栈空间通常是 2MiB,Linux 通常是 8MiB。这样如果 dfs 太多层会超过栈空间上限运行时错误崩溃。好消息是 NOI 系列正式比赛都会开无限制栈空间,不超总内存限制就行。

测时间和内存

问:如何测自己程序时间和内存?

答:Linux 用 time ./程序名 测时间,ulimit -v <内存限制KB数> 测超内存限制。Windows 用程序内 clock() 测时间,用命令行 cmdsize 测内存。

Linux 编译好程序 aa 后用 time ./aa 测时间(要定向输入输出就 time ./aa < aa.in > aa.out),看的是几个值中的用户时间(user);用 ulimit -v 524288 限制内存到 512MiB,建议实际开 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 这个词。

不要写超标语法

问:用超标/被禁语法爆零了怎么办?

答:平时就别写超过 -std=c++14#pragma 之类的违禁品。

至于有啥?自己开编译器 -std=c++14 测!NOILinux 默认这个不用开。

样例

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

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

如果你想看:

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

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