卡常相关

卡常太难了

链接:论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--]);
}
posted @ 2020-08-26 19:30  JiaZP  阅读(267)  评论(0编辑  收藏  举报