关于『卡常』
更快、更高、更强——更团结。
f
a
s
t
e
r
,
h
i
g
h
e
r
,
s
t
r
o
n
g
e
r
a
n
d
t
o
g
e
t
h
e
r
.
\tt faster,higher,stronger \ and \ together.
faster,higher,stronger and together.
怎样更快?
在程序设计中,往往会寻求代码的运行速度。
当你面对
T
L
E
\tt TLE
TLE 的残酷现实却不愿承认自己的算法有问题时,卡常是一个很好的办法。
卡常又称卡常数、底层常数优化,是
O
I
\tt OI
OI 中一种针对程序基本操作进行空间或时间上优化的行为。
1.快读 & 快写
总所周知,字符输入输出比标准输入输出快的很。
快读和快写就是运用了这个原理。
快写模板(循环):
template
void read(T &x) {
x = 0;
int f = 1;
char ch = getchar();
while(!isdigit(ch)) { //isdigit在
if (ch == '-') {
f = -1;
}
ch = getchar();
}
while(isdigit(ch)) {
x = x * 10 + (ch - 48);
ch = getchar();
}
x *= f;
return;
}
用法:
read(x); //输入并赋值
快写(递归):
template
void write(T x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
return;
}
用法:
write(x); //输出
2.位运算
能用位运算就用。
在乘除运算上,最好用左移和右移代替。
2 * 2 可写成 2 << 1, 2 * 10 可写成 (2 << 1) + (2 << 3)。
90 / 2 可写成 90 >> 1, 128 / 16 可写成 128 >> 4。
小知识:
位运算符号位运算关键字版符号&bitand``~compl^xor&=and_eq`=`^=xor_eq
位运算的其他应用:
1)代替模运算
17 % 2 可写成 17 & 1,45 % 4 可写成 45 & 3, 986 % 16 可写成 986 & 15。
2)快速幂
快速幂模板:
int qk_pow(int x, int n){
if(n == 0) {
return 1;
}
int t = 1;
while(n) {
if(n & 1) { //用&代替%
t *= x;
}
n >>= 1; //用>>代替*
x *= x;
}
return t;
}
3)Swap函数
void Swap(int &a, int &b) {
a ^= b ^= a ^= b;
return;
}
这个代码很妙,可以细细品味。
3.inline
在声明函数之前写上
i
n
l
i
n
e
\tt inline
inline ,可以加快一下函数调用,但只能用于一些操作简单、调用频繁的函数。涉及递归,大号的循环等很复杂的函数,编译器会自动忽略
i
n
l
i
n
e
\tt inline
inline 。
如快读函数可写成:
template
inline void read(T &x) {
x = 0;
int f = 1;
char ch = getchar();
while(!isdigit(ch)) {
if (ch == '-') {
f = -1;
}
ch = getchar();
}
while(isdigit(ch)) {
x = x * 10 + (ch - 48);
ch = getchar();
}
x *= f;
return;
}
但有时
i
n
l
i
n
e
\tt inline
inline 会被忽略。
4.寄存器
假如你要拿个钥匙,一个在你裤兜里,一个在你车上,你拿哪一个?
就近原则。
从距离上来说,内存条相较于寄存器来说离
C
P
U
\tt CPU
CPU 更远。
在时间相等的情况下,时间与路程成正比。
从工作原理上来说,寄存器的工作原理比内存简单的多。
综上,寄存器与内存之间具有差异,但差异不大。因此利用寄存器来提高访问速度往往运用与经常被访问的变量。
例如:
for(register int i = 1; i <= 1000000000; i ++)
register 关键字表示将变量 i 存于寄存器中。代码中变量 i 将被访问
1000000000
\tt 1000000000
1000000000 次,每一次访问都节省若干时间,乘上
1000000000
\tt 1000000000
1000000000 便不可忽略。在这个代码中优化后会快
1.5
sec
→
1.7
sec
\tt 1.5 \sec \to 1.7 \sec
1.5sec→1.7sec 。
5.三目运算符
i
f
e
l
s
e
\tt if \ else
if else 的汇编代码:
而三目运算符的汇编代码:
相较于
i
f
e
l
s
e
\tt if \ else
if else 语句,三目运算符的汇编指令更短。
在少量的运行中,二者的区别微乎其微。
但运行次数庞大时, 三目运算符的优势便显现出来。
如
M
i
n
\tt Min
Min 函数
int Min(int a, int b) {
return a > b ? a : b;
}
问号前是判断条件,如为真则执行冒号前的语句,否则执行冒号后的语句。
6.常数的声明
尽量将常数声明成常量。
如:
const int mod = 100000007;
比
int mod = 100000007;
要快。
7.#pragma 指令
经常提到
O
2
\tt O2
O2 这一说法,它的实现为:
#pragma GCC optimize(2)
当然还有
O
1
、
O
3
、
O
s
\tt O1、O3、Os
O1、O3、Os。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#pragma GCC optimize(s)
还有一堆:
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
重点:开个O2就行了,欲速则不达。(指令集开多了会有负面作用)
8.一些奇奇怪怪的卡常小知识
后置 ++ 需要保存临时变量以返回之前的值,在 STL 中非常慢。事实上,int 的后置 ++ 在实测中也比前置 ++ 慢 0.5 倍左右。
逗号比分号要快(神奇)。
b
o
o
l
\tt bool
bool 很慢,尽量开
i
n
t
\tt int
int(疑惑)。
一些卡常在正规比赛中 禁用 。
“行了,你已经是世界上最快的男人
O
I
\tt OI
OI 选手了。”