1. 程式人生 > >非演算法原因的時間超限&&輸入&&輸入優化&&編寫標頭檔案

非演算法原因的時間超限&&輸入&&輸入優化&&編寫標頭檔案

非演算法原因的時間超限&&輸入&&輸入優化&&編寫標頭檔案

輸入優化的引入&&優缺點介紹

在輸入輸出較多的題目中,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 "(標頭檔案名)"

即可。