浅论 OI 中的卡常技巧(更新中)

发布时间:2024-12-25 21:25

论文写作中的数据处理技巧 #生活技巧# #学习技巧# #学术论文写作#

好像是八百年前的版本了,[新版](https://zhuanlan.zhihu.com/p/608989466)

绪言

何为“卡常”?

卡常数,又称底层常数优化,特指在OI/ ACM-ICPC等算法竞赛中针对程序基本操作进行的底层优化,一般在对程序性能要求较为严苛的题目或是在算法已经达到理论最优时间复杂度时使用,有时也用于非正解的强行优化。 ——百度百科

然鹅,本文讨论的卡常并不都是底层常数优化,还有一些比较“表面的”常数优化。

同时,本文参见“实用主义”,许多东西不能从底层说清楚(主要是我太菜了),只是说说怎么用。

本文尽量按照从易到难,从广泛到罕见的顺序。

一、I/O 优化

1.输入

没有什么可说的,非常简单,就是fread>read>scanf=cin(关同步)>cin(普通)

值得注意的是,关同步有一些不太好的特征。比如:

<code class="language-plaintext hljs">ios::sync_with_stdio(0); cin.tie(0); cout<<45<<' '; cin>>a; cout<<a<<' '; printf("%lld ",a+6); cout<<a+7<<' '; printf("%lld ",a+8);</code>

输入2,结果为 8 10 45 2 9。

这是因为 sync_with_stdio(0) 解绑了本身 c++ 为了防止 cout 和 printf 而制造的绑定,虽然加快了速度,但会使得 cout 晚于 printf 执行。

同时,cin.tie(0) 也解绑了 cin 和 cout ,使得必须 cin 执行结束后才能有 cout ,这在评测中无妨,但调试时候有个这东西就很难受。

因此,笔者的建议是,在有 printf 和 cout 时,或者在调试时,完全不要用这种读入优化。没必要的话,也不要用 cin.tie(0)。

对了,如果快读还嫌慢的话,又不会 fread ,可以在没有负数时删去快读的负数判定(其实没有什么意义但卡常本来不也没有什么意义吗(雾。

2.输出

输出比较友好的一点是,cout 就很快,多数时候甚至比快写快,基本上只比 puts 和 putchar 慢。

问题是怎么在不输出空格的前提下利用 puts 的高速的特点呢? C++Reference告诉了我们答案:用 fputs 。

fputs("asdfghjkl",stdout);

效率比 puts 还吓人。即使把那个 \n 补回来,还是比 puts 快。

经过一系列测试,发现 fputs 在一次输出 2 个字符时跟 putchar 差不多快,输出的字符越多优势越碾压。于是又有了一条建议:如果要一次输出多个字符串的,请合并输出。因为调用 fputs 这个操作本身很费时。fputs("abcdefghijklmnopqrstuvwxyz",stdout);的耗时在我的电脑上只有fputs("",stdout);的两倍。这一点也适用于 printf。

另外,cout<<endl; 比 cout<<'\n'; 慢很多,主要是第一个还要清缓存。

二、变量类型

1.

众所周知,int 比 long long 快,int 比 short 快,int 和 char 差不多。

其实主要是 long long 慢,所以可能的话不要 define int long long ,这确实可以防止不开 long long ,但要是 MLE 或被卡常的话就寄了。而且都思考卡常了应该不会忘了开 long long 吧(大概。

float ,double 等也类似。

2.关于 const

const 确实会快,这主要是底层方面的原因了。这玩意写普通数开 O2 也不会改成 const ,所以必须手写。

而且有一个好处:你改了的话会报错。

至于 const 和 define 那个快这个需要进一步验证()。

另外 const 别乱开,开太多不仅没意义,有时还更慢。所以模数,哈希数,模拟退火啥的开就行了。

三、if-else 与 switch-case

一般来说,当分支较多,case 值连续紧密时,switch 会非常快。

多快?

我做的测试是这样的,switch 的判断条件是 5 个时,大约是 if-else 时间的 1/3 。而当分支的数量越多时,他们的性能相差就越大。

原理上似乎是这样的:

如果分支很少(好像是小于 3),编译器会考虑编译成 if-else 。

而分支很多时,分两种情况,case 值紧密,会用类似基数排序的方式记录 case ,是 O(1)

case 值疏松,会采用二分的方法。

所以无论如何分支多时,switch-case 会比 if-else 快。

四、位运算

其实用处不大,主要是 O2 ,但还是说一说吧因为有意思(雾

(下次更新写)

五、inline 与 register

实际上没有什么用,同样因为现在都开 O2。

在变量名前 register 意为暂存进寄存器,如果频繁调用的话会更快。但是在开 O2 的情况下,需要频繁调用的地方计算机会默认给你放进计算器,自己加了太多 register 可能反而适得其反,造成了负优化。

inline,可以使调用函数更快捷。但是在 O2 的情况下,计算机编译时自动回帮你内联的,如果你无脑给包括递归函数在内所有函数添加 inline,也会造成负优化。

六、内存接近

(下次更新写)

七、并行算法与循环展开

(下次更新写)

八、指针与动态内存

(下次更新写)

九、其他

1.优化取模

(下次更新写)

2.++i

(下次更新写)

3.玄学

(下次更新写)

网址:浅论 OI 中的卡常技巧(更新中) https://www.yuejiaxmz.com/news/view/566642

相关内容

[OI咨询]:2024户外生活方式趋势与机遇洞察报告
常数优化技巧
更高更妙的常数优化技巧
网卡中的一个小技巧
驳《驳〈论OIer谈恋爱的必要性〉》
浅谈陶行知生活教育理论在教学中的应用
摩卡色与卡其色哪个更显白?穿搭技巧揭秘,让你瞬间焕发亮白魅力
论文《浅析日常生活中的网络安全》.doc
浅谈数学在生活中的应用论文
论文浅谈艺术欣赏与生活中的艺术

随便看看