12.10 讓 cin、cout 變快
有些題目輸入輸出量很大——10^5、10^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.2 的 auto |
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 秒。交三次:① 照你平常的寫法(cin/cout 加 '\n',不加開場白)② 補上開場白 ③ 把 '\n' 換成 endl。三次的判定結果與執行時間擺在一起,本節的兩個重點就都親身驗證過了。