快速讀入
1、為什么要有快讀
好吧,有些題目看上去十分簡單,例如https://www.luogu.com.cn/problem/P4305這道題,實際上數據量巨多,光是一個測試點就可能有幾個MB,在這種情況下,就連scanf和printf函數都會超時Σ( ° △ °|||)︴我當初用scanf寫時TLE了3個點。我才不會告訴你我是用unordered_map水過去的
所以我們需要找到另外的讀入數據的方式。這時就要用到我們平時忽視的一個函數了——getchar()。你肯定會感到驚訝,但是我可以毫不猶豫地告訴你,這玩意確實比上述兩個讀入方式快許多。我們先一個蘿卜一個坑,從讀入int型開始慢慢做。
前面主要講運行原理,如果是需要直接拿代碼的,可以調到最后面去查看
2、輸入int
有時輸入並不會像你想的那么簡單,常常會出現一些空格或回車。如果從當前字符直接開始讀的話,就會多讀入一些空格或者回車,導致數據出錯。所以我們需要一個循環先過濾掉前面一些不需要的字符。
char ch = ' ';//初始化超重要的 while(ch < '0' || ch > '9') { ch = getchar();//此處用ch來充當變量 }
我們似乎要考慮一個情況,如果出現負數,會怎么樣?沒關系,在這個循環里加入一個判斷,來確定是正數還是負數:
char ch = ' ';//初始化超重要的 int w = 1;//是1就是正數 while(ch < '0' || ch > '9') { if(ch == '-') w = -1;//是-1就是負數 ch = getchar();//此處用ch來充當變量 }
我這段代碼有一個隱藏BUG。先讓讀者觀摩幾分鍾,然后查一查BUG。
BUG就是:如果輸入數據時" - 9",它也會判斷為負數!不過沒關系,很多題目的輸入數據已經保證不會出現這種情況。如果還不放心,可以自己改一改(其實是我懶了)
目前已經將前面多余的字符過濾掉了,現在要處理的是后面數字部分。此時我們要引用一個新的變量s來存儲所輸入的數字。處理輸入也很簡單,不斷輸入直到不再是1~9之間的數:
while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0',//讀入的數據是字符,需要減去一個ASCLL碼 ch = getchar(); }
如此,我們最后返回數據就可以了。全代碼:
inline int IntRead()//內聯函數稍微快一點點 { char ch = getchar(); int s = 0, w = 1; while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0', ch = getchar(); } return s * w; }
int main() { int a; a = IntRead(); cout << a; }
(等會,我好像忘記打include了)
3、輸入string字符串
處理完int輸入,字符串輸入相對來說簡單多了。首先先處理完前面多余的回車與空格,然后再不斷讀入直到讀到回車或空格。這里不再贅述直接上代碼:
inline string StringRead() { string str; char s = getchar(); //處理多余回車或空格 while (s == ' ' || s == '\n' || s == '\r') { s = getchar(); } //不斷讀入直到遇到回車或空格 while (s != ' ' && s != '\n' && s != '\r') { str += s; s = getchar(); } return str; }
4、讀入浮點數
終於到一個稍微復雜一點的地方了,讀入浮點數。讀入浮點數有兩個策略,1是先讀入字符串然后再進行處理,2是讀入的過程中,先讀整數部分,然后再讀小數部分。這里我選擇第二種。
前面處理多余字符和判斷負數的方式與int相同,只是后面讀入數字過程中要改變一下。我們引入兩個變量n、k和m,n存儲當前是讀整數部分還是小數部分,k表示小數部分的值,m表示小數部分的長度(可省略,但為了方便閱讀)。(這樣寫或許過於復雜?)如果讀到小數點,便修改n的值,否則就看n的值來讀入整數或小數。
最后返回答案時,直接將k轉換為小數,然后加上整數部分,然后再決定是返回負數還是正數。
代碼:
inline double DoubleRead() { //double的值可能很大,所以開long long long long s = 0, w = 1, k = 0, n = 0, m = 0; char ch = getchar(); //和int一毛一樣有木有 while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); } while((ch >= '0' && ch <= '9') || ch == '.') { //n = 0代表讀入整數,= 1代表讀入小數 if (ch == '.') n = 1; else if (n == 0) s = s * 10 + ch - '0'; else k = k * 10 + ch - '0', m++; ch = getchar(); } return (pow(0.1, m) * k + s) * w; }
大致的讀入方法就OK了,但是實際上我並沒有對代碼做優化。
快速讀出
說起來也奇怪,我的一位信息老師在換行或者只打印單個字符時,特別喜歡用putchar,我起初並無法理解這樣的意思。但是當我成功將快讀寫出來時,我也理解了老師的用意。
有一些題目依舊十分刁鑽,輸出量巨大,雖然我還沒有找到例題,但可以肯定的是絕對有一些不懷好意的出題人會將數據量加大,所以,快速讀出代碼就閃亮登場了!
1、快速讀出int
還是一點一點從0開始做起吧。
這里先放代碼
inline void IntWrite (int s) { int k = 0, len = 0; if (s == 0) putchar('0'); while (s) { k = k * 10 + s % 10; s /= 10, len++; } for (int i = 0;i < len;i++) { putchar(k % 10 + '0'); k /= 10; } }
先一行行開始講。如果數字是0,直接將0輸出沒毛病。我們寫的時候會發現一個問題:我們無法得知開頭的數字是什么!在不知道數字長度的時候,我們就計算不出開頭的數字。那么我們換個角度:從結尾處開始計算。得到數結尾的數字很簡單,直接對10取模就可以了。於是我們用一個變量k來存儲s翻轉過后的值,然后再將k翻轉輸出。比如將12翻轉成21,然后就可以順利輸出。不過,遇到100這個數字,翻轉后會成為1!我們就可以引入一個len來保證輸出的數不會出錯。
2、輸出string
輸出string就更加簡單了!直接上代碼:
inline void StringWrite(std::string str) { int i = 0; while (str[i] != '\0') { putchar(str[i]), i++; } }
或許有人會疑惑為什么是以'\0'來判斷而不是以.length來獲得string長度,因為后者的時間復雜度比前者高(想一想,為什么)。
3、輸出double
輸出double也不難,先將double轉換為long long int型,轉換過程中記錄小數點在第幾位。
inline void DoubleWrite(double a) { int mi = 0, s[100]; if (a == 0) putchar('0'); while (a != (long long int)a) { a *= 10, mi++; } long long int k = a, len = 0; while (k != 0) { s[len] = k % 10, len++, k /= 10; } for (len -= 1;len >= 0;len--) { if (len == mi - 1) putchar('.'); putchar(s[len] + '0'); } }
這里可能會有人有一些疑問:為什么前面輸出int型時不用數組呢?因為double型可以有很多小數,一旦小數數位超過一定閥值就會爆long long int(也就是溢出),所以用數組存。
所有代碼

inline int IntRead() { char ch = getchar(); int s = 0, w = 1; while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0', ch = getchar(); } return s * w; }

inline string StringRead() { string str; char s = getchar(); while (s == ' ' || s == '\n' || s == '\r') { s = getchar(); } while (s != ' ' && s != '\n' && s != '\r') { str += s; s = getchar(); } return str; }

inline double DoubleRead() { long long s = 0, w = 1, k = 0, n = 0, m = 0; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); } while((ch >= '0' && ch <= '9') || ch == '.') { if (ch == '.') n = 1; else if (n == 0) s = s * 10 + ch - '0'; else k = k * 10 + ch - '0', m++; ch = getchar(); } return (pow(0.1, m) * k + s) * w; }

inline void IntWrite(int s) { int k = 0, len = 0; if (s == 0) putchar('0'); while (s) { k = k * 10 + s % 10; s /= 10, len++; } for (int i = 0;i < len;i++) { putchar(k % 10 + '0'); k /= 10; } }

inline void StringWrite(std::string str) { int i = 0; while (str[i] != '\0') { putchar(str[i]), i++; } }

inline void DoubleWrite(double a) { int mi = 0, s[100]; if (a == 0) putchar('0'); while (a != (long long int)a) { a *= 10, mi++; } long long int k = a, len = 0; while (k != 0) { s[len] = k % 10, len++, k /= 10; } for (len -= 1;len >= 0;len--) { if (len == mi - 1) putchar('.'); putchar(s[len] + '0'); } }
最后
這一篇隨筆算是比較短的,但是我還是花了一個下午的時間去碼代碼,但可能依舊有一些小錯誤。一些代碼復制上來的時候我並沒有仔細去檢查,如果你發現了錯誤,歡迎在評論區里留言。