12.10 讓 cin、cout 變快

有些題目輸入輸出量很大——10^510^6 行起跳。這時常有人發現:演算法明明沒問題,卻 TLE 了,兇手是輸入輸出本身太慢。本節就是解藥,也是開場高手程式碼最後兩行謎底。

兩行開場白

ios::sync_with_stdio(false);
cin.tie(nullptr);

各自在做什麼,一句白話講完:

  • ios::sync_with_stdio(false);——斷開與 C 輸入輸出的同步附錄 B 說過,cin/cout 預設隨時跟 scanf/printf 對帳,確保混用時順序正確;這個對帳每筆輸入輸出都要做,很花時間。一句話關掉它,cin/cout 就能用自己的快取全速前進。
  • cin.tie(nullptr);——斷開 cin 與 cout 的綁定。預設每次 cin 讀之前,會先把 cout 累積的輸出送出去(設計給「先印提示訊息、再等使用者輸入」的互動程式);競程的輸入輸出各走各的,這個貼心是純浪費。nullptr 是「不指向任何東西的指標值」(11.2 的指標家族)——把綁定對象設成「沒有」。

效果實測(本站,讀入 10^6 個整數並加總):

讀入方式 時間
cin(預設) 0.25
cin(加了兩行開場白) 0.05
scanf 0.07

五倍差距——而且加了開場白的 cin 不輸 scanf。從此「cin 比較慢」的都市傳說對你失效:慢的不是 cin,是同步

兩個使用守則:開場白要寫在 main 最開頭、所有輸入輸出之前(同步斷一半會出怪事);另外它只需要寫一次,不是每次讀入都寫。

endl vs '\n':輸出的隱形煞車

輸出還有一腳煞車藏在你天天寫的 endl 裡。輸出其實不是直接送到螢幕——先攢在緩衝區(buffer),滿了才整批送出,這樣快得多。而 endl 做兩件事:換行,加上強迫立刻清空緩衝區(flush)。每行都 flush,等於每行都逼快遞員只載一件包裹就出車:

for (int i = 1; i <= 1000000; i++) cout << i << endl;   // 每行都強迫出車
for (int i = 1; i <= 1000000; i++) cout << i << '\n';   // 攢滿再送

本站實測輸出 10^6 行:endl 版約 2.8 秒,'\n' 版約 0.05 秒——五十倍。這就是為什麼本書從上冊第一單元起就教你寫 '\n';也有高手乾脆 #define endl '\n'12.9 的文本取代),把手滑的可能性直接定義掉。程式結束時緩衝區會自動清空,不必擔心最後一批輸出漏送。

回到開場的那份程式

現在把「想像一下」的高手程式碼再貼一次——這次你有解碼手冊了:

程式碼 解碼
#include <bits/stdc++.h> 12.1 萬用標頭檔
#define SZ(x) (int)((x).size()) 12.9 巨集(防 10.4 無號陷阱;這份程式其實沒用到它——高手的模板常備品)
using pii = pair<int, int>; 12.7 型態別名
ios::sync_with_stdio(false); cin.tie(nullptr); 12.10 開場白
for (auto &[a, b] : v) cin >> a >> b; 12.4 range-based for + 12.5 結構化綁定 + 12.3&(讀入要別名)+ 12.2auto
sort(v.begin(), v.end()); 11.4 的老朋友,素顏出場
for (auto [a, b] : v) cout << ... << '\n'; 只讀不改:不加 &;輸出用 '\n' 不用 endl

一行不剩。這份程式做的事,仍然只是「讀入、排序、輸出」——語法糖沒有讓程式變聰明,只是讓寫的人省力、讓懂的人秒懂。

動手試試看:解掉 NTUCPC OJ 的〈輸入輸出練習〉——讀 N 個整數再原樣印出來,N 上限 5 \times 10^6、時限 1.5 秒。交三次:① 照你平常的寫法cincout'\n',不加開場白)② 補上開場白 ③ 把 '\n' 換成 endl。三次的判定結果與執行時間擺在一起,本節的兩個重點就都親身驗證過了。