转载请说明出处:http://blog.csdn.net/leader_one/article/details/78430083
首先
常数是个谜,卡常是件很烦的事,被常数坑死的OIer已经不少了
常数不可避免,但是可以理性地去优化
当时间复杂度已经难以优化时,考虑常数优化
C++一些常数常见坑
I/O读入和输出
如果量小倒也没什么,如果大规模读入或者输出,C++自带的方式是很慢的
->首先,拒绝cin/cout,实在是太慢了,受不了
->接着scanf/printf,较慢,中小规模是可以的,但是百万级的I/O常数影响就大了
->所以考虑读入输出优化,百万级的I/O可以和scanf/printf相差0.X秒,和cin/cout则有几秒差距
以int类型为例
inline int read()
{
int X=0,w=1; char ch=0;
while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
return X*w;
}
这是读入,利用getchar()来进行字符读入是比较快的
输出同理,putchar()
inline void write(ll x)
{
if(x==0){ putchar('0'),putchar('\n');return;}
if(x<0){ putchar('-'),x = -x; }
char s[22],l = 0;
while(x!=0) s[++l] = x%10+48,x /= 10;
fow(i,l,1) putchar(s[i]);
putchar('\n'); //这里输出换行了,可以改
}
->不怕死的也可以用更快的fread(),fwrite()
-
寻址
由于没有开O2优化,会导致一些本来没有区别的变得比较明显。
多维数组把大的数放前面:
例如 int f[10000][1000][100] 而不是 f[100][1000][10000],跑起来差距0.Xs。
有时比算法的差距还大(开了O2后差别不明显)当然,比赛时一般没有O2,所以要注意–这是大坑
-
变量类型
例如 int和long long
int是4B的,32位,而long long是8B的,64位,所以在大规模运算时时间消耗上会有很大差别
能用小的就尽量别用大的
还有C++自带的string,常数实在是大,所以还是建议自己打char[ ]
-
C++自带STL
如果条件允许,最好还是自己手动实现,因为C++自带的STL常数很迷,有时可能大得惊人甚至导致TLE
所以,能自己手动实现最好还是自己写
当然,如果时间不允许或者不会写,用用也没关系
Others
位运算优化
可以使用一些位运算来做一些运算的常数优化
例如:
x*10 => (x<<3)+(x<<1)
x!=y => x^y
x!=-1 => ~x
x*2 => x<<1 (其他2的幂数同理)
x*2+1 => x<<1|1
x/2 => x>>1
(x+1)%2 => x^1
x%2 => x&1
x%2==0 => ~(x&1)
以上是一些常用的,还可以自己想
-
inline
讲真,inline挺神奇的,加上就可以优化函数/过程的常数
不过,是针对非递归形式的
inline void calc() //不是非递归的没什么用
直接加在前面就好了
-
乘/除/模
这三种运算常数是比较大的,尤其是除和模,原则上还是要减少使用
-
三目运算符
C++唯一的三目运算符 ? :
A?B:C 绝对是要比 if(A)B;else C要快的
-
函数/过程值得传递
例如 int calc(int a,int b)
如果只是传入一两个int、char什么的差距倒不明显,但如果是个string…显然就大了许多
可以使用全局变量或者&(直接用地址)来优化
-
循环内的问题
E.g1
for(int i = 0; i <= n; i++)
{
work1();
work2();
}
E.g2
for(int i = 0; i <= n; i++) work1();
for(int i = 0; i <= n; i++) work2();
运行效率哪一个快呢?
应该大多数人都觉得是第一种快,因为它少了一遍变量枚举-
其实这是片面的,如果work1( )和work2( )运算量都比较大的话,是第二种更快
这要由计算机的硬件说起。
由于CPU只能从内存在读取数据,而CPU的运算速度远远大于内存,所以为了提高程序的运行速度有效地利用CPU的能力,在内存与CPU之间有一个叫Cache的存储器,它的速度接近CPU。而Cache中的数据是从内存中加载而来的,这个过程需要访问内存,速度较慢。
这里先说说Cache的设计原理,就是时间局部性和空间局部性。时间局部性是指如果一个存储单元被访问,则可能该单元会很快被再次访问,这是因为程序存在着循环。空间局部性是指如果一个储存单元被访问,则该单元邻近的单元也可能很快被访问,这是因为程序中大部分指令是顺序存储、顺序执行的,数据也一般也是以向量、数组、树、表等形式簇聚在一起的。
看到这里你可能已经明白其中的原因了。如果work1和work2的代码量很大,例如都大于Cache的容量,则在代码1中,就不能充分利用Cache了,因为每循环一次,都要把Cache中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了Cache,利用两个循环语句,每个循环所用到的数据几乎都已加载到Cache中,每次循环都可从Cache中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出work1的数据1次即可。
-
局部变量和全局变量
之前我也是一直觉得定义全局变量是要比定义局部变量要快的…
其实不然,还是要从硬件设计说起
因为局部变量是存在于堆栈中的,对其空间的分配仅仅是修改一次寄存器的内容即可(即使定义一组局部变量也是修改一次)。而局部变量存在于堆栈中最大的好处是,函数能重复使用内存,当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中,访问速度非常快。而静态变量却不存在于堆栈中。
当然,大变量(例如大数组)还是全局定义吧,局部绝对是会炸的。
-
三个补充
1.memset( )底层是用汇编实现的效率要比直接的循环初始化快几倍左右
2.像下面这种代码复杂度是o(nL)的,L为str的长度。
for(int i=0;i<strlen(str);i++)
因为每次循环完后判断条件是否满足时都会重新计算一次strlen( )
同理,一些奇奇怪怪的需要大运算的东西就不要写在那里了,最好提前准备好
3.对于运算量比较小的计算式,几个运算写在一条式子会更快(可能不是快一点)
X = A+B; X = X%mo; 是没有 X = (A+B)%mo; 速度快的
在循环量大的时候有奇效!
结尾
–作者也是一个蒟蒻,本文有自己的心得也有参考别的大佬的博文,大佬名单就不一一列举了。
–同时,希望本文能对大家有所帮助。
——如果本文有误,欢迎各位指正