OI中C 常數問題及其優化

2021-08-10 05:53:41 字數 3730 閱讀 9457

常數是個謎,卡常是件很煩的事,被常數坑死的oier已經不少了

常數不可避免,但是可以理性地去優化

當時間複雜度已經難以優化時,考慮常數優化

i/o讀入和輸出

如果量小倒也沒什麼,如果大規模讀入或者輸出,c++自帶的方式是很慢的

->首先,拒絕cin/cout,實在是太慢了,受不了

->接著scanf/printf,較慢,中小規模是可以的,但是百萬級的i/o常數影響就大了

->所以考慮讀入輸出優化,百萬級的i/o可以和scanf/printf相差0.x秒,和cin/cout則有幾秒差距

以int型別為例

inline int read()

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)

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

所以,能自己手動實現最好還是自己寫

當然,如果時間不允許或者不會寫,用用也沒關係

位運算優化

可以使用一些位運算來做一些運算的常數優化

例如:

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++)

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( )

同理,一些奇奇怪怪的需要大運算的東西就不要寫在那裡了,最好提前準備好

3.對於運算量比較小的計算式,幾個運算寫在一條式子會更快(可能不是快一點)

x = a+b; x = x%mo; 是沒有 x = (a+b)%mo; 速度快的

在迴圈量大的時候有奇效!

OI中C 的常數優化詳解

ppt版本 常數優化有效性測試根據前文,我們有必要評估函式的耗時,從而進行有效的優化。使用標頭檔案cti me ctime ctime或tim e.htime.h time.h 中的clock 函式,此函式返回當前的總執行時間。樣例 include int tmp clock function in...

整數乘法優化問題(乘以常數) 轉

如果n為偶數,則將它除以2,如果n為奇數,則將它加1或者減1。問對於乙個給定的n,怎樣才能用最少的步驟將它變到1。其實這個問題就是如何將整數變數乘上整數常數問題轉化為最少的移位運算和加法運算問題.假設乙個計算機只提供加減法和移位一位的指令,那麼這個問題就相當於如何用最少的加減法和移位指令來計算乘上乙...

八皇后問題及其優化

在棋盤上放置8個皇后,使得它們互不攻擊,此時每個皇后的攻擊範圍為同行同列和同對角線,要求找出所有解,乙個很簡單的排列組合問題,每行每列每斜列只能有乙個皇后 created by max on 17 5 14.public class eightqueen public static void loc...