非演算法原因的時間超限&&輸入&&輸入優化&&編寫標頭檔案
非演算法原因的時間超限&&輸入&&輸入優化&&編寫標頭檔案
輸入優化的引入&&優缺點介紹
在輸入輸出較多的題目中,C++的選手會莫名其妙地出現Time Limit Exceeded(即TLE,時間超限)的評測結果(本人也曾經有過),於是對比AC程式,發現除了輸入輸出使用的函式和格式以外,幾乎一模一樣。
百思不得其解之後,便瘋狂地求助,但大家(由於都沒有經歷過)很多人(特別是Pascal的)都不知道是什麼問題,包括AC的人。於是,那位可憐的同志只好求助於網際網路。
查完以後,他捶胸頓足,氣憤得不得了——cin,cout速度過慢。
編者也做了一個實驗:100以內的n個數,以檔案為例,cin和scanf的區別。
資料構造程式碼如下(n可修改):
#include <cstdio> #include <cstdlib> #include <ctime> #define n 1000000 #define max_num 100 using namespace std; int main(){ freopen("data.in","w",stdout); int i,x; printf("%d\n",n); for(i = 1; i <= n; i ++){ x = rand() % max_num; printf("%d\n",x); } fclose(stdout); return 0; }
scanf程式碼:
#include <iostream>
#include <cstdio>
using namespace std;
int main(){
freopen("data.in","r",stdin);
int x,n,i;
scanf("%d",&n);
for(i = 1; i <= n; i ++)
scanf("%d",&x);
fclose(stdin);
return 0;
}
cin程式碼:
#include <iostream> #include <cstdio> using namespace std; int main(){ freopen("data.in","r",stdin); int x,n,i; cin>>n; for(i = 1; i <= n; i ++) cin>>x; fclose(stdin); return 0; }
以當前n的值為例,scanf需要0.375s。cin呢?用了3.899s。如果讀者有興趣可以在自己的電腦上也做一下對比實驗,程式碼已經在上面寫著了,可以用,也可自己寫。
我們繼續我們的故事,當那位同志得知了以後,捶胸頓足,發誓如果可以不用,就再也不用cin和cout,而用scanf和printf(他也的確做到了)。此後,那位同志就再也沒出過這樣的情況。時光流逝,他的程式設計技術極快地長進著,過了一年,他就已經可以做很難的題了。不過多久,他又出現了類似的情況(這次並不是因為他用了iostream),他用極限資料執行一下,AC的正好關進,他的正好被卡掉。他很快發現,AC的程式碼加了讀入優化,當然也變長了許多。從此以後他便看時間卡不卡,如果很卡,就加讀入優化加快程式執行速度,否則就不加保持程式編寫速度。
(此劇終。)
讓我們也再做一個實驗吧,如果你剛剛做實驗出的資料還沒刪掉,那麼你還可以向我這樣繼續做:加一個讀入優化的程式碼,用老的資料測時間。
老的程式碼就不貼了,讀入優化程式碼如下:
#include <cstdio>
using namespace std;
inline void read(int & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
int main(){
freopen("data.in","r",stdin);
int x,n,i;
read(n);
for(i = 1; i <= n; i ++)
read(x);
fclose(stdin);
return 0;
}
執行出來:0.093秒!
程式及其原理
講完了讀入優化的優缺點與必要性,現在回過頭來講讀入優化的原理。
首先,getchar是一個很快的讀入函式(可惜只能做字元讀入)(就像putchar是一個很快的輸出函式(一句廢話)),其次,在一個數後加一位後,這個數就成了這個數的十倍加上新加的一位,用位值原理可以證明。(考慮到諸位的數學水平,這裡就不證明了,以免出醜。)
雖然不是很長,但是寫起來很費時間,也很容易錯。(從來就是一遍對的大佬請忽略這句話)所以,我想把它先寫下來,要用了拷一遍就好了。以下是好多程式碼(也很好懂,有符號就是讀入是判是否有負號即可)(運用了C++函式的自動過載):
#include <cstdio>//getchar
using namespace std;
inline void read(long long & x){
int f = 1;
char c = getchar();
x = 0;
while(c < '0' || c > '9'){
f = 0;
if(c == '-')
f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = x * 10 + (c - '0');
c = getchar();
}
x *= f;
}
inline void read(int & x){
int f = 1;
char c = getchar();
x = 0;
while(c < 48 || c > 57){
f = 0;
if(c == '-')
f = -1;
c = getchar();
}
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
x *= f;
}
inline void read(unsigned long long & x){
char c = getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read(unsigned int & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read(char & c){
c = getchar();
}
inline void read_unsigned(int & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read_unsigned(long long & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read_letter(char & c){
c = getchar();
while((c < 'A' || c > 'Z') && (c < 'a' || c > 'z'))
c = getchar();
}
inline void read_small_letter(char & c){
c = getchar();
while(c < 'a' || c > 'z')
c = getchar();
}
inline void read_big_letter(char & c){
c = getchar();
while(c < 'A' || c > 'Z')
c = getchar();
}
標頭檔案
寫完不過癮,因為拷拷太麻煩,乾脆寫成標頭檔案。
經過長時間的研究,本人想到了怎麼寫:
前面加上
#ifndef _H_INCLUDED//_H前加標頭檔案名的大寫
#define _H_INCLUDED//同上
最後加上
#endif
code blocks有一種方便的方法:檔案\新建\專案,在跳出的視窗中的左欄找到檔案,再點標頭檔案即可,隨後按提示輸入資訊即可創建出標頭檔案,最後在中間的空行上寫程式碼即可。
標頭檔案如下:
#ifndef INPUT_H_INCLUDED
#define INPUT_H_INCLUDED
#include <cstdio>
using namespace std;
inline void read(long long & x){
int f = 1;
char c = getchar();
x = 0;
while(c < '0' || c > '9'){
f = 0;
if(c == '-')
f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = x * 10 + (c - '0');
c = getchar();
}
x *= f;
}
inline void read(int & x){
int f = 1;
char c = getchar();
x = 0;
while(c < 48 || c > 57){
f = 0;
if(c == '-')
f = -1;
c = getchar();
}
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
x *= f;
}
inline void read(unsigned long long & x){
char c = getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read(unsigned int & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read(char & c){
c = getchar();
}
inline void read_unsigned(int & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read_unsigned(long long & x){
char c=getchar();
x = 0;
while(c < 48 || c > 57)
c = getchar();
while(c >= 48 && c <= 57){
x = x * 10 + int(c - 48);
c = getchar();
}
}
inline void read_letter(char & c){
c = getchar();
while((c < 'A' || c > 'Z') && (c < 'a' || c > 'z'))
c = getchar();
}
inline void read_small_letter(char & c){
c = getchar();
while(c < 'a' || c > 'z')
c = getchar();
}
inline void read_big_letter(char & c){
c = getchar();
while(c < 'A' || c > 'Z')
c = getchar();
}
#endif // INPUT_H_INCLUDED
呼叫時將標頭檔案放到主程式的資料夾中,在主程式前加上
#include "(標頭檔案名)"
即可。