淺談超快讀
阿新 • • 發佈:2021-10-16
inline int read() {
int x = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar());
for (; c >= '0' && c <= '9'; x = (x << 3) + (x << 1) + (c ^ 48), c = getchar());
return x;
}
這是一個我們平常使用的快讀,他也能幾乎達到所有題目的要求。但是有一些題目,比如沒有加單調佇列的跳房子,在我校機房的土豆評測機上經常會被卡掉一個點,而且時間就差一點,於是超快讀就進入了我的必備模板。
我們知道,getchar
是每次讀出一個字元,若是我們能一次性讀進一大串字元,那樣效率就會快很多。
cstdio
裡有一個fread
函式正好能滿足我們的需要。這個函式的原型是這樣的:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
看不懂沒關係,我們慢慢解釋。第一個形參為讀入資料存放的位置,我們一般用陣列指標傳入;第二個形參為每個資料的長度,單位是位元組,如char
就是1
;第三個形參是理想情況下(輸入資料量充足)讀入多少個數據;第四個形參是讀入資料的位置,也就是指定輸入流,通常為stdin
。這個函式的返回值為實際讀入資料的個數。
那麼,稍稍改進一下我們的getchar
char buf[1 << 20], *_now = buf, *_end = buf; //buf是緩衝陣列,就是讀入的一串資料存放的地方;_now是字元指標,指向當前想取的那個字元;_end也是字元指標,指向這一串字元的最後一個。 inline char getchar() { if (_now == _end) {//如果這一串資料處理完了 _end = _now = buf; _end += fread(buf, 1, 1 << 20, stdin);//那就再讀一串,注意指標要回歸原位 if(_now == _end) {//如果沒有讀進來,就說明沒有資料了 return EOF; } } return *_now++; }
把這個加到快讀的前面就成超快讀了,當然getchar
的命名要改一下。
你可能會說,我的快讀已經很長了,再加上這一段,豈不是……
沒事,接下來我們來壓下程式碼,需要用到,
和&&
兩個運算子。
首先是,
運算子,這個我們一般在寫條件時用到,用,
並列的語句,判斷真假時只取最後一個。
for (int i = 1; i <= 10, i <= 14, i <= 6; ++i)
cout << i << endl;
輸出是:
1
2
3
4
5
6
其次是&&
運算子,這裡有一個短路求值的概念,即當且僅當左側運算物件無法確定表示式的結果時才會計算右側運算物件的值。
int a = 0;
if(10 > 20 && (a += 1, 1));//後半句沒有參與表示式的運算
if(10 < 20 && (a += 2, 1));//後半句參與了表示式的運算
cout << a << endl;
輸出為:
2
所以程式碼就壓成了:
char buf[1 << 20], *_now = buf, *_end = buf;
inline char _getchar() {
return _now == _end && (_end = (_now = buf) + fread(buf, 1, 1 << 20, stdin), _now == _end) ? EOF : *_now++;
}
當然你也可以使用巨集定義,這樣就不用改getchar
的命名了。
char buf[1 << 20], *_now = buf, *_end = buf;
#define getchar() (_now == _end && (_end = (_now = buf) + fread(buf, 1, 1 << 20, stdin), _now == _end) ? EOF : *_now++)
寫在最後:
- 此快讀一般用於檔案讀寫。
- buf陣列開小了會影響速度(會讀很多次),開大了會MLE,所以請根據題目和輸入資料範圍隨機應變,一般 \(2^{20}\) 到\(2^{23}\) 為適。
- 附一張讀入十萬隨機資料的速度:
輸入方式 | cin | scanf | cin(解綁) | 快讀 | 超快讀(buf[1<<2] | 超快讀(buf[1<<20] |
---|---|---|---|---|---|---|
速度/ms | 140 | 110 | 23 | 34 | 15 | 5 |