zerojudge e080. read and write

題目請參考 https://zerojudge.tw/ShowProblem?problemid=e080.

我們通常解題, 都是依序讀取測資的, 如果像這題測資很大, 就要把測資存在某種資料結構裡. 但是這題加上了使用記憶體的限制. 看似怎麼樣都找不出足夠的記憶體來儲存測資. 如果有辦法可以先讀測資的第二個數字, 再讀測資的第一個數字, 問題就解決了.

一般測資我們都是依照輸入的先後順序讀取來自stdio的stdin(標準輸入), 讀完就消失了, 怎麼可能先讀後面呢? 其實是有可能的! 如果測資很大而且是存在檔案裡, 再利用重新導向(redirection)的方法餵給你的程式, 那你就可以把stdin當檔案一樣來任意讀取.

我開一個DOS視窗, 執行這題的解題程式給你看看, 測資存在檔案裡若用重導的方法可以正確執行, 同樣的一個程式, 測資用打字輸入就失敗了.

現在可以開始解題了, 我一開始用getchar()來一個byte一個byte的先讀走前面的數字, 最後雖然可以執行成功, 但是時間超過限制. 為了增加讀寫速度, 我改用fread()和print()就成功了. (fwrite是zerojudge被禁用的)

fread是一次讀取一個block的程式, 所以要在每個block裡面去找兩個數字間隔開的那個空白, 同時為了加快速度, 我記錄下那個空白的位置, 這樣回去讀第一個數字時, 我就不用再找這個空白一次, 因為我已經知道讀到哪裡就可以結束了. 下面程式是先找第二個數字把他印出來.

	char a;
	bool start=false; //已經讀到第二個數字,可以開始印出

	int n;        //每次讀回的byte數
	int length=0; //紀錄空白的位置
	while(1){
		n=fread(buffer, 1, 1024*1024*2, stdin);
		if(!n)
			break;

		if(start){
			buffer[n]=0;    //先放個結束符號0, 再用printf印出
			printf("%s",buffer);
			continue;
		}

		length+=n;  //更新已讀的byte數
		//找第二個數字前的那個空白
		for(int i=0;i<n;i++){
			a=buffer[i];
			if(a==' '){     //找到了
				start=true;     //第二個數字開始
				length=length-n+i;  //紀錄空白的位置
				buffer[n]=0;    //先放個結束符號0, 再用printf印出
				printf("%s",&buffer[i+1]);
			}
		}
	};
C++

印完第二個數字後, 回去stdin的開頭.可以用fseek, 或是rewind, 我用rewind, 順便印一個空白. 後面印第一個數字我就不解釋了.

//印完第二個數字後, 回去stdin的開頭
	rewind(stdin);

	//依題目要求印一個空白
	printf(" ");

	//再印第一個數字
	int now=0;  //記錄讀了多少byte
	while(1){
		n=fread(buffer, 1, 1024*1024*2, stdin);
		if(!n)
			break;

		now+=n;
		if(now<=length){  //如果還沒超過第一個數字長度
			buffer[n]=0;  //先放個結束符號0, 再用printf印出
			printf("%s",buffer);
			continue;
		}
		//已超過第一個數字
		now-=n;
		buffer[length-now]=0; //先放個結束符號0,printf最後一段
		printf("%s",buffer);
		break;
	};
C++

完整程式如下, 這題我用2MB當buffer花了68ms AC. 其實不用這麼大也可以更快.你自己試試看吧.


#include <iostream>
#include <stdio.h>

using namespace std;

char buffer[1024*1024*2+1]; //一次讀1MB, 加一個byte放字串結束符號0.

int main(){

	char a;
	bool start=false; //已經讀到第二個數字,可以開始印出

	int n;        //每次讀回的byte數
	int length=0; //紀錄空白的位置
	while(1){
		n=fread(buffer, 1, 1024*1024*2, stdin);
		if(!n)
			break;

		if(start){
			buffer[n]=0;    //先放個結束符號0, 再用printf印出
			printf("%s",buffer);
			continue;
		}

		length+=n;  //更新已讀的byte數
		//找第二個數字前的那個空白
		for(int i=0;i<n;i++){
			a=buffer[i];
			if(a==' '){     //找到了
				start=true;     //第二個數字開始
				length=length-n+i;  //紀錄空白的位置
				buffer[n]=0;    //先放個結束符號0, 再用printf印出
				printf("%s",&buffer[i+1]);
			}
		}
	};

	//印完第二個數字後, 回去stdin的開頭
	rewind(stdin);

	//依題目要求印一個空白
	printf(" ");

	//再印第一個數字
	int now=0;  //記錄讀了多少byte
	while(1){
		n=fread(buffer, 1, 1024*1024*2, stdin);
		if(!n)
			break;

		now+=n;
		if(now<=length){  //如果還沒超過第一個數字長度
			buffer[n]=0;  //先放個結束符號0, 再用printf印出
			printf("%s",buffer);
			continue;
		}
		//已超過第一個數字
		now-=n;
		buffer[length-now]=0; //先放個結束符號0,printf最後一段
		printf("%s",buffer);
		break;
	};

}
C++

Comments

No comments yet. Why don’t you start the discussion?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *