浅谈算法竞赛中C++的常数优化
本人第一篇博客,就决定从这个话题写起了。
在正文开始之前,请注意:受限于作者的水平,本文只适用于初级的(NOIP-)算法竞赛。
以及,这里给出几个前提:
- 算法竞赛要考察算法!(👋😭👋)
- NOI大纲2025中明确注明要减少对算法常数的考察。
- 本文只面向算法竞赛的赛时代码。
- 在赛场上,你的时间是有限的。
- 在NOI系列赛事中,-O2 与 -std=c++14 已全面开启。
- 算法竞赛中,一切为了分数。
好了,下面是正文。
一些建议写的优化
大量定模数取模运算时的优化
如:题目要求你把结果对某个模数(如1e9+7)取模时。
这时,我建议你把模数设定为一个常量,即const int mod = 1e9+7,而不是直接把它设置成一个变量。
其中具体的门道我也说不太清,总之,模数写成常量形式后会更方便编译器去优化。
不需要动脑子就可以实现的输入输出优化
在开始编写你的代码前,建议把这几行代码写上去:
#define endl '\n'
ios::sync_with_stdio(false); cin.tie(nullptr);
然后,在后面的输入输出中,使用C++风格的输入输出(cin, cout)。
实际上,这些工作减少的代码常数已经足够让你在NOIP及以下的题目中不被卡常数了,下面是解释:
#define endl '\n'的作用是:将代码中所有的 endl 替换成 '\n'。
究其原因,是endl的常数实在是太大了。这点或许读者有所耳闻,但不知道具体大到什么程度,这里给出我在本地测试的数据。
在AMD Ryzen 7 8845HS w,-O2与-std=c++14环境下输出1e6个1后换行:
| \ | endl | '\n' | printf下的'\n' |
|---|---|---|---|
| synced | 1884ms | 79ms | 86ms |
| unsynced | 1835ms | 65ms(本文推荐写法) | 79ms(不生效) |
'\n'的好处已经不言而喻了,但或许这时会有人说:“你这里synced和unsynced的优化也不明显呀,我为什么要费劲写这一行呢?”
那是因为,关闭同步流(即ios::sync_with_stdio(false); cin.tie(nullptr);)的优化主要体现在输入上,下面给出测试数据:
在AMD Ryzen 7 8845HS w,-O2与-std=c++14环境下读入1e6个1:
| \ | cin | scanf |
|---|---|---|
| synced | 240ms | 200ms |
| unsynced | 84ms(本文推荐写法) | 204ms(不生效) |
这下,优势就显现出来了。
一些不建议写的优化
快读与快写
当然,这个时候或许有人要说:“那我写快读快写呢?”
这里,就不得不强调上面的那几个前提了。
先不说你能否在赛场上写出正确的快读快写,就算能写出来,10ms左右的优化是几乎不可能给你带来更多的分数的。
当然,我不否认在平常训练时学习快读快写会加深你对C++的理解,但本文面向的是赛时代码,所以我本人仍强烈建议:
抛弃快读快写,拥抱解除同步流的C++风格io。
火车头(#pragma ......)
NOI系列赛事不让用。
inline与register
开 -O2 后会被忽略。
简单的代数式优化
(如:将(i+3)*3写成i*3+9,将i/2写成i>>1等)
开 -O2 后编译器会帮你。
总结
在使用了上面这些优化方法后带来的效果已经足够你应付NOIP及以下的算法竞赛了,但如果你进入到了省选甚至NOI的赛场,请学习更深奥的常数优化方法,这里我推荐洛谷 深入浅出程序设计竞赛 提高篇中常数优化章节,里面涉及了很多进阶的常数优化算法,本文的灵感也来源于此书。

浙公网安备 33010602011771号