卡常太难了
链接:论OI中各种玄学卡常
卡常常识
为何卡常?什么时候要卡常?
通常像分块,莫队等等这样的根号算法以及某些常数巨大的算法(如 Splay 等)可能会被卡常。考试的时候非正解算法有时卡常能够拿到更多的分数(尤其是面对数据不是非常大,算法又常常跑不满的时候,可能会 (n^2) 过一万)。
平时保持卡常的小习惯,不要写那么多冗余代码也是比较重要的,同时对调试有利。
至于常数因素对程序的速度的影响究竟有多大,可以看这道题:#2989. 「CTSC2016」NOIP十合一
(的第一个测试点)。平时对于1e9的规模通常认为O(n)是过不去的,但是由于运算只有十亿次普通的加乘模,在本机五六秒就跑完了。
通常情况下,如果常数足够小,2s可以跑1e9(本机测试不开O2 int ct = 0; while(++ct <= 1e9);
2秒跑完,开O2 40ms 跑完,估计有编译优化)。不过在一般的题的常数下 (1s10^8) 还是可以的。如果是 Splay 的话就不好说了。
如何卡常?
-
对着复杂度瓶颈使劲卡,同为复杂度瓶颈的部分要优先对着常数较大的那部分使劲卡。不要一个 (O(n log n)) 的题只对着一个 (O(n)) 的部分卡
-
inline
read()
-
一般情况下递归要比循环慢一些,所以如果被卡常了或许可以用循环代替递归试一试。
-
当我们确定输入中不存在负数的时候,可以把快读中对负数的特判去掉。
-
减少冗余计算,尽量优化常数。必要时以空间换时间。
-
手写 STL 的 (stack, queue, deque)。 (vector) 也可能会被卡,但是不太好手写,不过还是可以替代的(如用邻接链表)
-
实在不行尝试深夜提交,或者多次提交碰运气
-
有时候删掉一些调试遗留下来的语句可以加快一下速度,毕竟虽然能保证不进入“if”,但是每次判断一下会拉慢速度。当然,正式考试的时候千万不要遗留调试语句,否则可能本来可能骗到分的测试点就自动弃权了。
-
可以尝试把DFS改成BFS,通常会快很多。如果想要多次自底向上遍历一棵树,可以记录拓扑序,每次逆拓扑序遍历,就不用递归了。
真“高性能”题:
P4135 作诗(没有优化的分块会被卡时间卡空间)
P4718 【模板】Pollard-Rho算法(毒瘤卡时间)
P3527 [POI2011]MET-Meteors(普通的整体二分会被卡时间然而我竟然用朴素算法卡过去了)
P3380 【模板】二逼平衡树(树套树)(卡Splay)
P3759 [TJOI2017]不勤劳的图书管理员(Splay被卡得狂T不止,需要用树状数组套线段树)
“另类”的卡常:卡空间
一些毒瘤题竟然毒瘤到卡空间!比如:P5471 [NOI2019]弹跳,P4148 简单题,P6622 [省选联考 2020 A/B 卷] 信号传递 ,P3592 [POI2015]MYJ
。这样的题并不多见,但是一出现就很致命,毕竟我并不太会卡空间。
可能用到的方法:
-
时间换空间。即,当我们用到的时候现场算。
-
int 改成 short,bool 数组用 bitset 维护
-
改变算法,如 st表 改成线段树,树套树换成K-D Tree。
-
...
附:
快速读入
template<typename T> inline void read(T &x) {
x = 0; char c = getchar(); bool flag = false;
while (!isdigit(c)) {if (c == '-') flag = true; c = getchar(); }
while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }
if (flag) x = -x;
}
快速输出(不常用,一般用printf即可)
template<typename T> inline void Write(T x) {
short stk[30], top = 0;
if (x < 0) putchar('-'), x = -x;
do stk[++top] = x % 10, x /= 10; while (x);
while (top) putchar('0' | stk[top--]);
}