常数优化技巧

 今天让我们整理一下一些常数优化技巧:

1. 读入优化:

#define sight(c) ('0'<=c&&c<='9')
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}
//使用方法 read(x);

这是一直基于getchar的快速读入。相比大家都会,不说了。

2.更快的读入优化:

#define getchar nc
#define sight(c) ('0'<=c&&c<='9')
inline char nc(){
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}

我们用buf数组把所有的输入都读入到buf数组里,还要快。(此后便不能用scanf和cin了,因为输入在buf数组里了)

3.如果我们大抵知道数据输入规模,我们可以这样写nc函数:

#define nc *p1++
char *p1=buf
fread(stdin,buf,1,100000);//主程序里加这句话 

4.同理,我们有输出优化:

void write(int x){if (x<10) {putchar('0'+x); return;} write(x/10); putchar('0'+x%10);}
inline void writeln(int x){ if (x<0) putchar('-'),x*=-1; write(x); putchar('\n'); }

要用的时候调用writeln。(在写write函数的时候要少写判断。)

5.不要信仰STL的常数(sort除外)。

#define min(a,b) (a)<(b)?(a):(b)
#define max(a,b) (a)>(b)?(a):(b)
#define sight(c) ('0'<=c&&c<='9')
#define swap(a,b) a^=b,b^=a,a^=b

必要的时候一定要手写(尤其是bitset,queue,stack)。

such as bitset。

struct bitsets{
    long long t[4];
    void set(int x){
        int aa,bb;
        aa=x/60;bb=x%60;
        this->t[aa]|=1LL<<bb;
    }
    void reset(){
        this->t[0]=0;
        this->t[1]=0;
        this->t[2]=0;
        this->t[3]=0;
    }
    void ad(const bitsets &g){
        this->t[0]&=g.t[0];
        this->t[1]&=g.t[1];
        this->t[2]&=g.t[2];
        this->t[3]&=g.t[3];
    }
    void oo(const bitsets &g){
        this->t[0]|=g.t[0];
        this->t[1]|=g.t[1];
        this->t[2]|=g.t[2];
        this->t[3]|=g.t[3];
    }
    void xo(const bitsets &g){
        this->t[0]^=g.t[0];
        this->t[1]^=g.t[1];
        this->t[2]^=g.t[2];
        this->t[3]^=g.t[3];
    }
    bool tr(const bitsets &g){
        bool top=true;
        top&=((this->t[0]&g.t[0])==0);
        top&=((this->t[1]&g.t[1])==0);
        top&=((this->t[2]&g.t[2])==0);
        top&=((this->t[3]&g.t[3])==0);
        return top;
    }
};

6.大规模的函数参数调用最好引用(&)

比如这样:

void X(int &x){
}

7.我们要学会手开O2

#define MARICLE __attribute__((optimize("-O2")))

那么我们就可以在要开O2的函数过程前加 MARICLE 。

或者在宏中定义以下宏

#pragma GCC optimize("-O2")

8.Ox优化并不是越高越好:

 我们发现Ox类的优化是有代价的。我们发现开着O类优化我们无法调试。

-O1 提供基础级别的优化

-O2提供更加高级的代码优化,会占用更长的编译时间

-O3提供最高级的代码优化

此外我们发现O3优化是以缩小栈空间为代价的,所以有递归的程序O2优化就够了,O3会更慢。

9.循环优化:

for (i=1;i<=b;i++)

这样写是很慢的。

for (int i=1;i<=b;i++)

这样写要快那么一丢丢,因为在循环体里面定义,编译器会把变量放到寄存器里,这样会更快。

for (int i=b;i;i--) //i从b到1
for (int i=b;~i;i--)//i从b到0

这样子更快。因为是位运算。

10.枚举子集。

 for (int i=0;i<siz;i++)
  for (int j=i;j;j=(j-1)&i)
   f[i]=.....

这样子写是O(3^n)的,比常规枚举要快。

11.相同语句下,从速度上讲,宏>重载运算符>函数。

12.没有递归的程序前可以加inline,这样更快。

inline void X(int &x){
}

 

就是这样子。

13.我们可以这样子用位运算来加速逻辑判断:

if (a^b) 等价于 if (a!=b)
if (!(a^b)) 等价于 if (a==b)

 最后声明,其实在Os优化中,这些优化差不多都有,其实然并软。

 好吧,我知道我写的很没有营养。还是举个栗子吧。

{以下测试都在一台配置如下的机子中:

      CPU: intel  i3-6100 3.7GHZ

      RAM: 4.00GB

      64 位操作系统

}

  随便写了个程序 :

#include<bits/stdc++.h>
using namespace std;
int n,a[300007],ans;
int gcd(int x,int y){
    return y?gcd(y,x%y):x;
}
signed main () {
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
     for (int j=i+1;j<=n;j++)
      ans=max(ans,gcd(a[i],a[j]));
    printf("%d\n",ans);
}

然后造了一个2W数据规模的数据。 运行了38.85s.

在其上方加入以下指令:

  #pragma GCC optimize("-O2")   28.48s

在编译器中加入O2优化指令: 28.22s

//以下操作都在O2的基础上

    在gcd函数前加入inline 指令  28.5s (大概是系统忽视了inline ,因为递归的函数加inline讲道理会很假)

     我们强制内联 :加入 __attribute__((always_inline)) 28.49s。

  把递归改成迭代:

#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
using namespace std;
int n,a[300007],ans,t;
int gcd(int x,int y){
    while (y) {
        t=x; x=y; y=t%x;
    }
    return x;
}
signed main () {
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
     for (int j=i+1;j<=n;j++)
      ans=max(ans,gcd(a[i],a[j]));
    printf("%d\n",ans);
}

结果并没有快多少,应该是O2自动把gcd展开了吧。

#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
using namespace std;
int n,a[300007],ans,t;
#define getchar nc
#define sight(x) ('0'<=x&&x<='9')
inline char nc(){
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}
void write(int x){if (x<10) {putchar('0'+x); return;} write(x/10); putchar('0'+x%10);}
inline void writeln(int x){ if (x<0) putchar('-'),x*=-1; write(x); putchar('\n'); }
inline void writel(int x){ if (x<0) putchar('-'),x*=-1; write(x); putchar(' '); }
inline int gcd(int x,int y){
    while (y) {
        t=x; x=y; y=t%x;
    }
    return x;
}
int p[13],ed,c[300007],pppp;
signed main () {
    freopen("a.in","r",stdin);
    read(n);
    for (int i=1;i<=n;i++) read(a[i]);
    for (int i=n;i;--i) {
       ed=8;
     for (int j=1;ed<i;j+=8,ed+=8){
         p[0]=gcd(a[i-j],a[j]);    c[j]>p[0]?:c[j]=p[0];
         p[1]=gcd(a[i-j-1],a[j+1]);  c[j+1]>p[1]?:c[j+1]=p[1];
         p[2]=gcd(a[i-j-2],a[j+2]);  c[j+2]>p[2]?:c[j+2]=p[2];
         p[3]=gcd(a[i-j-3],a[j+3]);  c[j+3]>p[3]?:c[j+3]=p[3];
         p[4]=gcd(a[i-j-4],a[j+4]);  c[j+4]>p[4]?:c[j+4]=p[4];
         p[5]=gcd(a[i-j-5],a[j+5]);  c[j+5]>p[5]?:c[j+5]=p[5];
         p[6]=gcd(a[i-j-6],a[j+6]);  c[j+6]>p[6]?:c[j+6]=p[6];
         p[7]=gcd(a[i-j-7],a[j+7]);  c[j+7]>p[7]?:c[j+7]=p[7];
      } 
      for (int j=ed-7;j<i;++j){
        pppp=gcd(a[i],a[j]);    c[j]>pppp?:c[j]=pppp;}
    }
    for (int i=n;i;--i) ans>c[i]?:ans=c[i];
    printf("%d\n",ans);
}

循环展开,但不知道为什么,并没有快。

应该是gcd太大了吧,反正循环展开就是这样的。

posted @ 2017-12-30 07:37  泪寒之雪  阅读(1601)  评论(2编辑  收藏  举报