[I/O 優化] C++ 篇
[I/O 優化] PYTHON 篇
在 OJ 解題時,
常常遇到 TLE(Time Limit Exceed) 的問題,
有些時候並不是因為方法錯誤,
而是在「輸入/輸出」花上太多的時間了,
以下介紹幾種基本的 I/O 優化方法。
< Method 1. >
在主程式裡面加上以下這兩行:
ios::sync_with_stdio(false);
cin.tie(nullptr);
基本上就是把原本預設與 cstdio 的同步關掉,還有解除 cin 的綁定。
並且把原本的 endl 換成 '\n' (換行字元),
詳細的解釋可以參考 這篇。
< Method 2. >
把原本的 cin, cout 換成 scanf(), printf(),
根據在 OJ 解題的經驗,
scanf(), printf() 在測資檔小的時候,
會比 < Method 1. > 快,
但是相對在寫程式時就比較不方便些,
格式也比較嚴謹,
撰寫相對麻煩,
斟酌使用。
< Method 3. >
用字元讀取、字元輸出的方式,
用 getchar(), putchar() 函式,
可以達到更好的執行效率。
程式碼如下:
template <typename T>
inline bool rit(T& re) {
re = 0; bool neg = 0; char c = getchar();
while (!isdigit(c)) {
if (c == EOF) return 0;
if (c == '-') neg = 1;
c = getchar();
}
while (isdigit(c)) re = re * 10 + c - '0', c = getchar();
re = (neg ? -re : re);
return 1;
}
template <typename T>
inline void wit(T x) {
if (x < 0) {
putchar('-'); x = -x;
}
char tmp[20]; size_t len = 0;
do tmp[len++] = x % 10 + 48; while (x /= 10);
while (len) putchar(tmp[--len]);
// putchar('\n');
}
要注意的是,這邊只處理「整數」的部分
如果傳入的是浮點數、字串等,
可能會發生預期以外的錯誤。
另外還有 getchar_unlocked(), putchar_unlocked() 兩個讀取字元的函式,
在部分系統和 OJ 可能無法使用,斟酌使用。
用法和 getchar(), putchar() 是一樣的,
但是速度可以差到非常多哦(約十幾倍 ~ 幾十倍)!
*最快的「讀入」函式 fread()
簡單來說,就是讀取檔案。
程式碼如下:
const size_t size = 200000; char buf[size], *p1 = buf, *p2 = buf;
inline char getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, size, stdin), p1 == p2) ? EOF : *p1++;
}
把原本的 getchar() 改成 getc() 就好了。fread() 有 4 個參數,
第 1 個是「讀入空間」,在上例中是字元陣列 buf ,
第 2 個是「讀入的單一元素記憶體大小」,在上例中是因為 sizeof(char)=1,故直接用 1 即可,
第 3 個是「一次要讀入的數量」,在上例中以 size 表示,
第 4 個是「讀入的檔案名稱」,OJ 基本上都是 stdin。
最後會回傳「讀取的數量」。
當然也有 fwrite(),但是很多 OJ 都禁止使用 fwrite(),這邊就不多介紹了。
< Method 4. >
streambuf,
用法與 < Method 3. > 的概念類似,
只是用的方法不一樣。
程式碼如下:
template <typename T>
inline bool rit(T& re) {
re = 0; bool neg = 0; char c = cin.rdbuf()->sbumpc();
while (!isdigit(c)) {
if (c == EOF) return 0;
if (c == '-') neg = 1;
c = cin.rdbuf()->sbumpc();
}
while (isdigit(c)) re = re * 10 + c - '0', c = cin.rdbuf()->sbumpc();
re = neg ? -re : re;
return 1;
}
template <typename T>
inline void wit(T x) {
if (x < 0) {
cout.rdbuf()->sputc('-');
x = -x;
}
char tmp[20];
size_t len = 0;
do tmp[len++] = x % 10 + 48; while (x /= 10);
while (len) cout.rdbuf()->sputc(tmp[--len]);
// cout.rdbuf()->sputc('\n');
}
一樣,只處理「整數」部分,但是 streambuf 的方法要加上 < Method 1. > 才會達到非常快哦!
總結:
以上幾個方法,
I/O 優化後,
時間都差非常多,
但是仍然有些差距,
這幾個方法的速度排名大約如下:
時間都差非常多,
但是仍然有些差距,
這幾個方法的速度排名大約如下:
- fread()+putchar_unlocked()
- streambuf 和 getchar_unlocked()+putchar_unlocked()
- scanf()+printf() 和 ios::sync_with_stdio(false), cin.tie(nullptr);
雖然在 zerojudge 這個 OJ 上,
很多題目就算演算法錯誤,
但加上了 I/O 優化,
仍然可以 AC,
但不要一昧的學 I/O 優化而不學習演算法,
這樣根本是本末倒置,
失去學習程式設計的意義了,
真的遇到有些題目卡 I/O 優化時再使用就好了。
另外,可以再把以上 I/O 優化的程式碼包裝,
程式碼如下:
struct _istream {
// #define getchar getc
bool eof; char buf[200000], *p1 , *p2;
inline operator bool() {
return !eof;
}
inline char getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 200000, stdin), p1 == p2) ? EOF : *p1++;
}
template <typename T>
inline _istream& operator>>(T& re) {
re = 0; char c = getchar(); bool neg = 0;
while (!isdigit(c)) {
if (c == EOF) {
eof = 1;
return *this;
}
if (c == '-') neg = 1;
c = getchar();
}
while (isdigit(c)) {
re = re * 10 + c - '0';
c = getchar();
}
re = (neg ? -re : re);
return *this;
}
_istream(): eof(0) {
p1 = p2 = buf;
}
} rit;
struct _ostream {
// #define putchar putchar_unlocked
inline _ostream& operator<<(const char c) {
putchar(c); return *this;
}
inline _ostream& operator<<(const char* s) {
while (*s != '\0') putchar(*s++);
return *this;
}
inline _ostream& operator<<(const string s) {
for (const auto& i : s)
putchar(i);
return *this;
}
template <typename T>
inline _ostream& operator<<(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
char s[25];
size_t len = 0;
do s[len++] = x % 10 + 48; while (x /= 10);
while (len) putchar(s[--len]);
return *this;
}
} wit;
#define cin rit
#define cout wit
#define endl '\n'
#define istream _istream
#define ostream _ostream
把這一串貼程式碼在最上面,等確定執行 OK 之後,在把 struct 裡面 // #define 的斜線拿掉即可。
這樣可以支援基本的 operator,
使用時只要 cin>>tmp, cout<<tmp 就好,
這樣可以支援基本的 operator,
使用時只要 cin>>tmp, cout<<tmp 就好,
相對用函式的方法方便。
一樣只適用於「整數」,
但也只支援基本的輸入、輸出的動作,
其他的成員函數是無法使用的。
以上,
但也只支援基本的輸入、輸出的動作,
其他的成員函數是無法使用的。
以上,
若有更好的想法歡迎提出哦!
留言
張貼留言