一道題帶你認識ACM競賽
說明:這是我們學校ACM公眾號的一篇推送,我感覺我寫的挺好的,順便放在部落格當紀念啦~!
以下是正文,排版原因,改成適合部落格閱讀的方式:
這一篇推送將帶你們真真正正走近ACM比賽的世界。
閱讀此文章你需要簡單的C或C++程式設計基礎,沒有程式設計基礎也沒關係,我們會由淺入深,用最簡單易懂的語言去介紹。
· 什麼是程式設計?
引用用百度百科裡面的介紹就是:
程式設計是編寫程式的中文簡稱,就是讓計算機代為解決某個問題,對某個計算體系規定一定的運算方式,是計算體系按照該計算方式執行,並最終得到相應結果的過程。
· 什麼意思呢?
就像我們解數學題,看完題目你會做了,你腦袋裡有思路,但是應該怎麼表達出來呢?這個時候你就要需要數學符號,用數學的語言去解釋這道題目應該怎麼解。然後數學老師就會看懂你的話,並理解你的思路。
同樣的,我們在編寫程式的時候,我們就需要一種計算機的語言,計算機能明白能看懂並執行的語言。我們所說的程式設計,就是用計算機的語言,告訴計算機去做什麼。我們平常所提到的 C語言,C++語言,Python語言,JAVA語言,就是計算機常用語言。
我們的ACM比賽就是用計算機語言,去解決各種問題,類似於數學競賽,用數學語言去解決數學問題。所以ACM是一個挑戰性極強的競賽!接下來,我將用一道簡單但又不簡單的題目,帶你們瞭解ACM。
母牛的故事
從前有一個農夫,他的名字叫做約翰。他養了很多很多頭母牛。突然有一天,一隻調皮的母牛走丟了,農夫要儘快的抓住她,不然她就又跑掉了!現在我們將問題簡單化。假設農夫和母牛都站在一條數軸上,農夫開始的位置為N,母牛的位置為K。
約翰有三種行動方式,每行動一次需要一秒鐘時間,假設農夫的現在的位置為X,他可以向前走一格到X+1,也可以向後走一格走到X-1,他還可以傳送!一下子走到了2*X。
那麼我們的問題是,假設母牛不會動,農夫最少需要多少秒才能抓到母牛?
輸入:輸入包括兩個整數,用空格隔開,分別為N和K。其中0<=N,K<=100000。
輸出:一個整數T,代表農夫所需的最少時間。
以上就是我們常見的ACM題目描述啦!如果用數學的方式去描述,就是:已知N和K,N,K為題目描述的意思,求T。各位小夥伴可以思考一下怎麼做。對於這種題目,我們很難用數學的方法去證明或解決。這個時候我們只能藉助計算機去幫助我們解決這個問題。這個時候就需要一些程式設計的思想了,對於沒有接觸過計算機程式設計的同學,理解起來可能會有點難度。
· 題目大意
這裡我們給出一組資料,假設N=5,K=17,那麼最後我們計算得到的應該是T=4.
怎麼計算得來的呢?假設我們採用全部往前走的策略,那麼我們一共要走17-5=12步。這個策略明顯是不對的。因為我們一開始就讓5×2=10,這樣可以省去很多步驟,那麼我是不是隻要乘得越多得到的步數就越少呢?
5→10→20→19→18→17
當農夫的位置變為20後,這個時候他能做的只有往後走,所以我們有這樣一個看似更優的策略,一直乘2直到X大於K,然後再算出往後走的步數。這樣的策略算出來是5(一共走了5步),這樣的策略明顯跟正確的答案不一樣。實際上我們有更優的策略!
5→10→9→18→17
這個時候就複雜了,我們無法用正常的語言,或者說數學的語言去描述這種策略。這個時候應該怎麼辦?只能藉助計算機的語言了!我們知道計算機的計算能力非常強大,我們人類不知道最優策略的時候,就可以讓計算機直接幫我們暴力算出來。這個時候我們就要編寫程式,讓電腦幫我們計算。
對於程式設計未入門者,讀到這裡就已經足夠了!如果你已經瞭解程式設計,或者你對程式設計感興趣,可以繼續往下閱讀。
· 題解
實際上,我們直接讓計算機幫我們計算出每一種情況就可以了。我們知道一開始的位置是5,那麼我們可以把所有情況都列舉一遍,即試一下往前走1往後走1或乘2。然後我們再對得到的三種情況4,6,10繼續重複上面的步驟得到5,3,8,7,5,12,11,9,20 。這樣我們不斷地得到一個越來越大的數列,最後只要看看這個數列裡面有沒有K,就代表我們是否到達了K,這個時候我們只要輸出我們重複了多少次這個步驟,就是答案了。
聰明的孩子或許已經想到,這其實是一個寬度優先搜尋演算法,我們不斷地嘗試每一種情況,直到找到正確答案為止。程式碼打起來有點複雜而且有很多的細節需要考慮。這裡我講解幾個重要的細節。
-
當農夫的位置為0的時候,他只能往前走1。
-
當農夫的位置大於K時,他只能往後走1 。
-
如果某個位置農夫已經走過,那麼農夫不用再走到該位置。
讀者可以思考一下為什麼。如果沒有考慮第三點,程式會超時。第三點其實就是我們演算法課上講的剪枝,我們不必搜尋我們已經搜尋過的地方。最後就是我們打程式碼的時候了!更多細節,注意看程式碼的註釋。程式碼採用C++。
· 程式碼
#include<iostream>
#include<queue>//用系統提供的佇列
using namespace std;
int N,K;
//當前狀態,X為農夫當前的位置,T為農夫走過的次數
struct point{
int X;
int T;
};
//用於標記該位置農夫是否走過
bool vis[200050];
//題目說N<=100000,所以我們要把陣列定義成比100000*2大一點點的大小,防止陣列越界!(有興趣的可以用思考下為什麼)
int main(){
//輸入N和K
cin>>N>>K;
//初始狀態
point a;
a.X=N;
a.T=0;
queue<point> que;//定義一個佇列(寬搜基本上都使用佇列)
que.push(a);//將該節點(即初始狀態)加入佇列
//將陣列初始化為0,即一開始農夫所有位置都沒有走過。
for(int i=0;i<200050;i++)
vis[i]=0;
//不斷地提取狀態,題目保證有解
while(!que.empty()){
point temp=que.front();//臨時變數
que.pop();
//如果農夫當前的位置就是K,那麼輸出T並退出程式。
if(temp.X==K){
cout<<temp.T<<endl;
return 0;
}
//如果往後走不是0,並且往後走的那個位置沒有被走過,那麼我們就把往後的情況加入到佇列
if(temp.X-1>=0&&vis[temp.X-1]==0){
point tt=temp;
tt.X--;//往後走
tt.T++;//時間加1
que.push(tt);//加入佇列
vis[temp.X-1]=1;//標記為走過
}
//往前走的情況
if(temp.X<K&&vis[temp.X+1]==0){
point tt=temp;
tt.X++;
tt.T++;
que.push(tt);
vis[temp.X+1]=1;
}
//乘2的情況
if(temp.X<K&&vis[temp.X*2]==0){
point tt=temp;
tt.X*=2;
tt.T++;
que.push(tt);
vis[temp.X*2]=1;
}
}
return 0;
}