其他-一些自己總結的卡常技巧
阿新 • • 發佈:2019-01-11
偶然發現自己程式碼的常數還算小?
於是乎總結了一下自己發現的一些常數技巧(還沒寫完,後續會更)
各位看官請耐心看完……前面都是大家知道的,後面會寫些自己發現的東西
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<<1
,x/2
替換為x>>1
,x%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
的檔案,裡面即是程式這次在各個函式內執行的時間,然後就可以找到那個拖累程式的函式進行優化了