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)

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

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

結尾

–作者也是一個蒟蒻,本文有自己的心得也有參考別的大佬的博文,大佬名單就不一一列舉了。
–同時,希望本文能對大家有所幫助。
——如果本文有誤,歡迎各位指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章