[0.0] UVa 10071

http://luckycat.kshs.kh.edu.tw/homework/q10071.htm
http://uva.onlinejudge.org/external/100/10071.html

一樣是善良的一題!需要記得一點..國中物理,不記得也沒關係我會帶著大家解!
這題標準的練習多重輸入用,只要手算出公式其它都不是問題!
最麻煩的地方在於它並不會告訴你何時輸入結束,也不會預先告訴你總共有幾組輸入,批改娘高興丟幾組就丟幾組!

幸好批改娘會用檔案的方式丟,檔案大小是有限的,你家使魔只要有辦法在限定時間內,讀到檔案結尾並處理完所有輸入就沒問題啦!
這邊我們會教你如何處理這種方式,即使現在逐漸不使用這種輸入方式,但在UVa這堆老題目中,還是相當常見的。


這會牽扯到scanf()的行為。其實它是有回傳值的,只是我們很少去用它。
scanf()回傳一個整數,代表它成功讀入東西中,有多少個存入了給定的位址。

以下我們來做個實驗。google可以告訴你很多事,但能不能從認識到理解到應用自如的招式又是另一回事了。
其中實際去設計並實驗,多跟牠用不同方式玩玩,就可以從中了解其個性和行為!
int a, b, c;
c = scanf("%d%d", &a, &b);
printf("%d\n", c);

我們如果輸入兩個或以上數字,前兩個會被讀掉,並分別存入a和b之中,總共兩個,輸出結果會是2。
如果我們只輸入一個數字呢?照理說,我們期待結果是: 讀入一個數字存到a去。
但是你會發現,你的程式會乖乖地坐下來搖尾巴耐心等候你輸入第二個數字。WTF!?

小黑窗輸入並不像檔案一樣,一開始就有既定的內容以及確切的結尾,但小黑窗輸入不是;
就像你看一本書,從頭到尾就那些東西,但如果今天你聽的是說書的方式,那麼講者可以選擇講多講少、講慢講快,甚至來個中場休息你也不能怎樣。他講多快,你就只能聽多快。他愛停多久你也沒有辦法,他又不是不講了,只是想喝杯水。
如果你預期今天聽他兩個章節的故事,他只講了一個章節就停下了,你當然期待再聽第二個章節,除非他明言沒有後續然後走人。
如果他講三個章節而你今天只想聽兩個,當然聽完兩個就可以直接走人了。

對於你家使魔而言,小黑窗輸入就像聽你說書,檔案輸入就像直接給牠一本書讓他讀。
在你應該並未說完(預期是兩個整數而你只輸入了一個)又未能確定你已經不想說了的時候,使魔所能做的只有耐心等候。
可是檔案輸入還有檔案結尾可以看,小黑窗的手動輸入怎麼辦呢?

所以我們必須主動輸入檔案結尾 (EndOfFile, EOF) 讓使魔了解已經沒有後續了。
輸入方法是按CTRL+Z,畫面上會出現^Z的字樣,ENTER按下去就會真的輸入進去了。
使魔就會停止scanf()的等待,認定只輸入了一個數字存入a,並繼續進行後續任務。
注意這時沒有任何數字被存入b,所以b的值會維持原樣;在此b並未被賦予過任何值,又是區域變數,所以會是未定義狀態,不會自動變成0喔!

為什麼說它是看存入的數量呢?我們改做以下實驗
c = scanf("%*d%*d");

%d是一個整數,可是%*d是什麼意思?也是整數,但差異在寫作%*d時代表讀入一個整數但不儲存它,所以就算我們放上&a和&b也不會存進任何數字。
打兩個整數上去,會得到c=0的結果。

回到前面的測試
c = scanf("%d%d", &a, &b);

如果遇到EOF會怎麼樣?我們不妨試試一開始就輸入EOF,所以c應該會是0。
WTF!? 實際測試發現c竟然是-1,這是怎麼回事!?
原來,scanf()如果一開始就撞到EOF,它就會傳回EOF (-1) 表示輸入已結束。
我們可以嘗試以下寫法:
printf("%d\n", EOF);
同樣會印出-1。

但是,如果我們先輸入一個空白,再輸入EOF,像底下這樣:
 ^Z
會因為有讀到一個空白,卻沒讀到任何整數可以存入a和b,所以回傳值會是0而非-1。

我們希望使魔可以持續讀到檔案結尾,因此我們可以這樣子寫:
while(scanf("%d%d", &a, &b) != EOF)
意思是,在尚未讀到檔案結尾時,持續進行。
同時為了判斷是否是EOF,每次while都要把scanf()執行完,才能拿到新的結果來判斷。而執行scanf()就會去嘗試讀兩個整數。
while(scanf("%d%d", &a, &b) != EOF)
{
}
printf("END!\n");

但這樣是很危險的,萬一我們輸入前面的空白加上EOF,牠就會永遠卡住,沒辦法到輸出END那一行。為什麼呢?
因為牠會找不到下一個整數,空白又不是我們要的東西,也不是作為兩個整數間的分隔使用。
所以牠並不會把非預期的空白給讀掉,而只會拿出來看一看發現情況不對,又乖乖放回去,並且回報讀不到東西(回傳值0)而不是檔案結束(回傳值EOF),while會認定需要繼續執行,可是進行下一次嘗試時,又會發生完全一樣的情形,最後就陷入所謂的無窮迴圈。

反正只讀到一個整數或讀不到,也不會是我們預期的結果。
比較安全的寫法是
while(scanf("%d%d", &a, &b) == 2)
當我們還能讀滿2個整數並分別存入a和b時,就繼續執行。當然麻煩的地方就是,我們必須自己填相對應的數字。

好,接下來我們嘗試解開這一題吧!
最後要求距離,設v0為初速,v1為末速,v為t秒後速度,a為加速度
(注意這裏方便起見以^代表次方,但^在C語言並不是次方的意思!)
由題目定義得到v=v0+at, 末速v1=v0+2at
等加速度公式v1^2=v0^2+2as
=> v1^2-v0^2=2as
=> v0^2+4atv0+4a^2t^2-v0^2=2as
=> 4atv0+4a^2t^2=2as
=> 2tv0+2at^2=s  (a=(v-v0)/t)
=> 2tv0+2(v-v0)t=s
=> s=2tv0+2tv-2tv0
=> s=2tv
好解完了。嗯?你問我怎麼教會你家使魔解這鬼東西?
不需要啊。使魔不擅長做的解方程式由你進行,最後教牠答案是2tv並讓他算出正確數字,完美的分工互助共度難關啊!

檢驗一下v和t的範圍,2vt不會超過,所以放心使用int解下去!
注意!如果這一題稍微改一下,v的範圍給到10萬,t也給到10萬,看起來都在int範圍內,但答案2vt乘起來可就不是這麼一回事了!
所以不能只考慮給的數字範圍,還要考慮計算過程以及最後結果會不會超出int可承受範圍喔!

如果還不知道如何上傳已經寫好的程式碼,可以先讀這篇:
[0.0] UVa 11172

那麼,一樣先預祝各位順利拿下這題啦!
附帶一提,之後我會越來越傾向避開程式碼,只告訴各位如何解開一道題,思考的方向以及方式,讓大家用自己的方式寫出正確的程式碼,而不要被我的程式碼所影響,在你們心裏留下一份陰影,寫的時候下意識地干擾你們思考。
自己從零開始思考如何建構、組合各種語法,包括邏輯以及流程,是很重要的一件事。我們必須學習掌握自己程式中每一行程式碼!

如果一開始做不到,可以嘗試慢慢將計算與運作流程畫成簡圖,再一步步寫成程式。就像畫個草圖後,一步步用積木堆成城堡一般!
過程難免會出bug,不過沒關係,出bug也是學習的一環!除了學習怎麼找出bug並修正,同時也在學習什麼情形會出bug,甚至怎麼寫容易出bug。
錯得越智障、找得越久越崩潰,對它的印象就會越深刻,再犯的機會就會降低,即使犯了也能更快找到牠!
所以出bug可是學習成長的好機會喔!記得好好把握!

沒有留言:

張貼留言