1. 程式人生 > >一道題帶你認識ACM競賽

一道題帶你認識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,這個時候我們只要輸出我們重複了多少次這個步驟,就是答案了。

       聰明的孩子或許已經想到,這其實是一個寬度優先搜尋演算法,我們不斷地嘗試每一種情況,直到找到正確答案為止。程式碼打起來有點複雜而且有很多的細節需要考慮。這裡我講解幾個重要的細節。

  1. 當農夫的位置為0的時候,他只能往前走1。

  2. 當農夫的位置大於K時,他只能往後走1 。

  3. 如果某個位置農夫已經走過,那麼農夫不用再走到該位置。

       讀者可以思考一下為什麼。如果沒有考慮第三點,程式會超時。第三點其實就是我們演算法課上講的剪枝,我們不必搜尋我們已經搜尋過的地方。最後就是我們打程式碼的時候了!更多細節,注意看程式碼的註釋。程式碼採用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;
}