首先说明一下,本人在一些代码中没有加的优化,是因为本人用不惯(其实是太懒)...
一:for与while的使用.
听说貌似for和while的速度差不多,但貌似while更容易接受编译器的优化什么的,据说是for多了一条jump语句?
所以多用while吧,但这个东西的时间复杂度没人会卡...
二:对于循环能够一次跳的多就多跳.
上面这句话是什么意思?
举个例子吧,比如说分解质因数,一般大家都写:
int num=n;
for (int i=2;i*i<=n;i++)
while (num%i==0) pr[++top]=i,num/=i;
if (num^1) pr[++top]=num;
我们其实可以发现,除了2以外所有的质数都是奇数,所以我们可以这样写:
int num=n;
while (~num&1) pr[++top]=2,num>>=1;
for (int i=3;i*i<=num;i+=2)
while (num%i==0) pr[++top]=i,num/=i;
if (num^1) pr[++top]=num;
这个卡常其实大多数情况下会很快.
三:尽量避免除和模等内部实现较慢的运算.
除和模在必须使用的时候确实要使用,但是有些时候我们可以避免.
比如说一开始a和b都是小于mod的数,现在要使c=(a+b)%mod,我们可以这样写:
c=a+b;
if (c>=mod) c-=mod;
再比如说上面分解因数的时候,我们一般写这么一个循环:
for (int i=1;i<=sqrt(n);i++)
但我们都知道sqrt其实很耗时,而且有些情况下sqrt会涉及到实数的精度问题,所以我们可以这么改:
for (int i=1;i*i<=n;i++)
一般来说比较耗时的运算有这么几个:
除法:/
取余(C/C++中是取余):%
开方:sqrt()
实数运算都会比较耗时
四:内联函数inline.
不得不说这个东西基本上还是会有优化的,但是即使无法优化的东西编译器会自动忽略的,所以放心用就好了.
注意,递归你就不用放进去了,放了也没用.
具体使用的话,比如定义了这么一个函数:
int abs(int a,int b){return a+b;}
那就写成这样:
inline int add(int a,int b){return a+b;}
五:加入前缀register.
这个东西据说十分的快,但是现在的编译器好像会自动加上这个了...
没事CCF的老年机...这个东西的原理就是将变量放到寄存器中加快速度.
大概就是在变量前面加一个register,不过一般在循环变量前加就可以了.
示例的话,比如说这么一个循环:
for (int i=1;i<=n;i++);
改成这样:
for (register int i=1;i<=n;i++);
六:循环展开.
这个东西据说CPU可能会同时运行,只要后面的运算对前面的运算没有依赖性就行.
什么是依赖性?比如说这么一串代码:
a=a+b;
c=b+a;
我们发现只有运行完a=a+b这条语句之后才能得到a的正确值,c=b+a这条语句才能继续运行,这就是依赖性吧.
循环展开的示例?比如这么一段代码:
for (int i=1;i<=n;i++)
for (int j=1;j<=5;j++)
a[i][j]+=a[i-1][j];
我们把里面那个循环拆开,写成这样:
for (int i=1;i<=n;i++){
a[i][1]+=a[i-1][1];
a[i][2]+=a[i-1][2];
a[i][3]+=a[i-1][3];
a[i][4]+=a[i-1][4];
a[i][5]+=a[i-1][5];
}
这样就会跑快许多.
七:少用括号.
其实不必要加的括号就不要加,比如说下面的语句:
a=1<<(n+1);
其实n+1不加括号也是会先运行的,所以可以改成这样:
a=1<<a+1;
这样就会跑的快一点.
其实大括号也是这个道理,比如说下面的语句:
for (int i=1;i<=n;i++){
a[i]++;
b[i]++;
}
其实是可以改成下面的语句的:
for (int i=1;i<=n;i++)
a[i]++,b[i]++;
八:前缀++和后缀++.
其实我们一般写的循环:
for (int i=1;i<=n;i++)
写成:
for (int i=1;i<=n;++i)
会跑的更快...
具体原因据说是因为i++要多存一个临时变量,而++i不用.
九:多用位运算.
对不起计算机底层实现是二进制...
所以位运算会跑的比一般运算要快...
比如说下面的代码:
if (n%2==0) return n/2%2;
其实写成下面的代码会更快:
if (~n&1) return n>>1&1;
但是如果你在位运算外面套了括号,其实位运算也没什么鸟用了...
十:多用三目运算符.
对不起if(条件) 语句1;else 语句2;比条件?语句1:语句2要慢.
具体例子就是把比如这段代码:
int gcd(int a,int b){
if (b) return gcd(b,a%b);
else return a;
}
给换成这样:
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
十一:看看你的机器是几位的.
如果你的机器是32位的,那么int这种东西会比long long什么的都快,因为int也是32位的.
而如果是64位的,那么long long有时比int还快...
我想现在应该没有16位机这种东西了吧,所以char和bool就可以用int和long long代替了...
十二:能把数字定义为常量就定义常量.
常量的关键字是const,这个东西加在一个量的前面就必须给这个量赋值,且这个值不能改变.
示例如下:
const int N=100000;
十三:尽量避免浮点数运算.
浮点数运算通常都很慢且容易出现精度错误,能不用就别用.
十四:各种奇怪的编译选项优化.
-O2,-O3,-O1,-O2.5都是优化开关,开了会跑的奇快.
-Os是把所有优化开关都开启的优化开关,与此同时还有一个都关闭的-O0.
这些东西的手动开启可以这样写:
#pragma G++ optimize(2) //开启-O2优化
#pragma G++ optimize(3)
#pragma G++ optimize(2.5)
#pragma G++ optimize(s)
#pragma G++ optimize(0)
#pragma G++ optimize(1)
十五:关于STL的运行速度.
STL这个东西如果没开启-O2优化就别用,会慢到死.
如果开了就随便用STL,因为只要开了这个优化就没人写的出比STL还要快的代码了...
十六:使用指针.
有些编译器下动态分配内存会比较慢,有些编译器下指针也会比较慢,但一般指针还是很快的,所以不用刻意避免这个问题.
十七:读入输出的神奇优化.
这个时候快读就很重要了,但是更快的是fread和fwrite.
快读大概就是这么一个模板:
int read(){
int x=0,y=1;
char c=getchar();
for (;c<'0'||c>'9';c=getchar()) if (c=='-') y=-1;
for (;c<='9'&&c>='0';c=getchar()) x=x*10+y-'0';
return x*y;
}
快输其实要么用递归要么用数组,代码如下:
void write(int x){
if (!x) return;
write(x/10);
putchar(x%10+'0');
} //递归版
void write(){
top=0;
while (x) s[++top]=x%10,x/=10;
if (top==0) putchar('0');
else while (top) putchar(s[top--]+'0');
} //数组版
至于fread和fwrite,这东西我不会用的,太迷了...
貌似还有个更快的mmap?我反正放弃了...
十七.寻址时的优化
寻址时的优化,就是尽量减小数组指针跳动的指针距离.
基本上这一类优化使用在数组调用上,比如说矩阵乘法的实现.
至于原理就是因为数组指针跳动距离变小了.
这一类优化可以通俗理解位内层循环调用的一维尽量开数组时开在前面.
例子就是f[i][j]循环调用时用两层循环:
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
f[i][j];
那么我们就应该开数组的时候是开f[j][i]而不是f[i][j],然后循环时这样调用:
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
f[j][i];