OI中C++常数问题及其优化

转载请说明出处: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)

以上是一些常用的,还可以自己想

扫描二维码关注公众号,回复: 2644165 查看本文章

-
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; 速度快的
在循环量大的时候有奇效!

结尾

–作者也是一个蒟蒻,本文有自己的心得也有参考别的大佬的博文,大佬名单就不一一列举了。
–同时,希望本文能对大家有所帮助。
——如果本文有误,欢迎各位指正

猜你喜欢

转载自blog.csdn.net/leader_one/article/details/78430083
今日推荐