1. 程式人生 > >其他-一些自己總結的卡常技巧

其他-一些自己總結的卡常技巧

偶然發現自己程式碼的常數還算小?

於是乎總結了一下自己發現的一些常數技巧(還沒寫完,後續會更)

各位看官請耐心看完……前面都是大家知道的,後面會寫些自己發現的東西

register

  • 將變數放在暫存器內,每次訪問速度更快
  • 定義在變數前(如:register int i=1;
  • 暫存器大小有限,放不下太多變數(放太多會導致反而變慢)
  • 不能定義為全域性變數
  • 單純迴圈\(1e9\)時,不加register跑需\(2s\),加了僅需\(0.2s\)
  • 建議在迴圈次數較多的for的變數前加register
  • 在一些比賽中會被自動過濾

inline

  • 將函式進行類似define的東西,使得訪問函式速度大大增加
  • 定義在函式名前(如inline int find(int x){return x;}
  • 若在有遞迴操作或有大量迴圈操作的函式前加inline是沒有用的
  • 建議在一些很短的函式前加inline
  • 在一些比賽中會被自動過濾

迴圈展開

  • 將迴圈內的東西分塊合併,例如:
  • for(int i=1;i<=n;++i)a[i]=1;
  • 可以變為
  • for(int i=1;i<=n;i+=4)a[i]=a[i+1]=a[i+2]=a[i+3]=1;
  • 展開越多貌似越快,注意不要陣列越界

前置++

  • 一般都用++i,而不是i++

少用if-else

  • 據學長所言,速度是\(if>(?:)>if\_else\)
  • 注意使用三目運算子的時候注意優先順序問題,之前就有過慘痛經歷

比如:

x?get1(),work1():get2(),work2()

看上去很像是:

if(x) {get1(); work1();}
else {get2(); work2();}

但實際上它是這樣的:

if(x){get1(); work1();}
else get2();
work2();

為什麼?因為它讀取到冒號後的第一個逗號就截止了,以為逗號後面是下一條語句

多用define

  • define的用處是替換程式碼,編譯程式的時候直接將程式碼替換後再編譯
  • 可以定義一些短函式,使得程式不用呼叫函式,比如#define f(x) (x*x)
  • 可以定義一些常量,比如#define p (1e9+7)
  • 可以用於精簡程式碼,比如#define For(i,j,k) for(int i=j;i<=k;++i)
  • 可以用於紀念,比如說#define Formylove return 0,最後就只要Formylove

注意事項

  • 謹慎使用define定義函式,比如定義#define min(x,y) (x<y?x:y)時,若x為一個遞迴函式,會使得程式呼叫兩次這個函式,若線上段樹上使用這個,會使得複雜度變為\(log^2n\)
  • 注意優先順序問題,比如定義#define f(x,y) x+y,如果外部呼叫為z*f(x,y),就會變為z*x+y

少用乘除

  • 據測試,運算子速度排名為位運算>加減號>乘法>除法>取膜
  • 所以儘量使用位運算(但\(x*10\)不要拆成\((x<<3)+(x<<1)\)
  • 多用加減號代替乘除(這個不多說)
  • 多用乘法代替除法(比如實數的/2可以替換為*0.5

快速讀入

  • 貼個板子就走人

考場實用型:

template <typename _Tp> inline _Tp read(_Tp&x){
    char c11=getchar(),ob=0;x=0;
    while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')ob=1,c11=getchar();
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}

int main(){read(x);}

刷速衝榜型:

struct ios {
    inline char read(){
        static const int IN_LEN=1<<18|1;static char buf[IN_LEN],*s,*t;
        return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
    }
    template <typename _Tp> inline ios & operator >> (_Tp&x){
        static char c11,boo;
        for(c11=read(),boo=0;!isdigit(c11);c11=read()){if(c11==-1)return *this;boo|=c11=='-';}
        for(x=0;isdigit(c11);c11=read())x=x*10+(c11^'0');boo&&(x=-x);return *this;
    }
} io;
int main(){io>>x;}

以下都是博主自己的經驗,不保證完全正確,dalao勿噴

標頭檔案

  • 根據博主長久以來的測試,發現標頭檔案定義的速度排名為:
  • 第一:單個常規標頭檔案
  • 第二:一個萬能標頭檔案
  • 第三:一堆常規標頭檔案
  • 所以給出建議,若只需一個頭檔案,就只打對應的那一個頭檔案,若需要不止一個頭檔案,打萬能標頭檔案bits/stdc++.h

定義變數

  • 定義一個變數是要時間的,可以將變數儘量地提到外面(如在for迴圈內定義變數不如在for迴圈外部定義,可以減少定義次數
  • 若能開全域性變數最好

合併語句

  • 將互不相關的語句進行合併,語句之間使用逗號相連
  • he=1;ta=1;q[1]=s;可以合併為he=ta=1,q[1]=s;可以進一步合併為q[he=ta=1]=s;

位運算

  • 聯賽選手都熟知的東西
  • x*2替換為x<<1x/2替換為x>>1x%2替換為x&1

下面是一些其他的技巧

矩乘

  • \(APIO2018\)的講課上提到過矩陣乘法的變數列舉順序最快為ikj(當然想更快可以矩陣分塊)
  • 如果想更快,可以使用前面提到的訪問優化
  • 即將\(A[k][j]\)儲存下來:
for(int i=0;i<A.n;++i)
    for(int k=0;k<A.m;++k){
        ll t = A[i][k];if(!t)continue;
        for(int j=0;j<B.m;++j)
            res[i][j]=(res[i][j]+t*B[k][j])%p;
    }

gprof

  • 這個是一個檢測自己程式執行效率的東西

舉個例子,若要檢視程式a.cpp的效率,可以輸入以下命令:

  • g++ -o a a.cpp -pg
  • ./a
  • gprof -b ./a > res

然後在對應資料夾裡就會出現一個叫res的檔案,裡面即是程式這次在各個函式內執行的時間,然後就可以找到那個拖累程式的函式進行優化了