計程新手指南

這篇文章是寫給計程新手的指南,雖然我自己也不是什麼強者,但還是分享一下我自己當初修計程時累積的技巧和經驗,希望可以對未來計程卡關的人有所幫助。

編譯方法

考試的時候是使用 VScode,沒有網路,所以你必須使用 cmd 來編譯。

  • 單檔案編譯
    把 file.c 編譯成一個叫 run 的檔案
    gcc file.c -o run
    執行 run
    ./run
    其中 file.c 是你的檔名,run 是編譯完的檔名,這兩個可以換掉。
  • 多檔案編譯
    把 file1.c 和 file2.c 編譯成一個叫 run 的檔案
    gcc file1.c file2.c -o run
    執行 run
    ./run
    其中 file1.c 和 file2.c 是你的檔名,run 是編譯完的檔名,這三個可以換掉。
    這個寫在課本的第 19 章,正常人根本一開始不會去讀到那邊,害我一開始不會編譯只能硬丟,結果吃一堆 CE,還好後來都有過。

編輯器

比起 VScode,我個人私心推 CP Editor,可惜考試時不能用,我自己平常練題的時候都是用這個,它是為了競程設計的 editor,所以有很多方便解題的功能。
不過當然,這篇文章不會強制要求用 CP Editor,其他的 editor 也是適用的。

錯誤訊息解讀

這邊會介紹常見的錯誤訊息和它們可能的成因。

CE (compiile error)

這大概是新手時期最常見的一個錯誤訊息,你可以從 terminal 中看到是哪裡出了問題,如果看不懂或是不懂為什麼報錯的話可以上網搜尋看看。等到對語法熟了之後這種問題就不太會是一個困擾了。

RE (runtime error)

成因

有很多原因會造成這個問題,不過比較有可能的大概是這幾個:

  1. scanf 的時候忘記加 &

    scanf("%d",n);

    當程式發生問題的時候(不管是 RE 還是其他的錯誤訊息),請第一個先檢查是不是忘記加 &。
    這看起來很蠢,對吧?
    但相信我,你不會希望你 debug 半天的結果是出在這種問題上面,我自己也被這東西搞過好幾次,所以請小心。

  2. 存取超過陣列範圍

    int array[5];
    printf("%d",array[5]);

    請記得 c 是從 0 開始數。
    另外,我通常會把數量設的比上限大一點來避免邊界的 RE 問題。

  3. 開超大陣列(也有可能會是 TLE)

    int array[10000000000][10000000000];

    一般情況下是不至於開到一個這麼大的陣列,會遇到這個狀況的大概是 hash table 的題目(以計程的範圍而言),像這樣:

    struct cube{
    char val[100];
    int posx,posy,posz;
    };
    struct cube tower[1000][1000][1000];

    你開了一個 struct 的陣列,然後就太大爆掉了,請改用指標來存這些資料,像這樣。

    struct cube{
    char val[100];
    int posx,posy,posz;
    };
    struct cube *tower[1000][1000][1000];
  4. 遞迴太深

    void endless(int i){
    printf("%d",i);
    endless(i+1);
    }

    在計程的範圍內,會出現遞迴太深的大概是不小心進入無限遞迴,在寫遞迴的時候,請確保有遞迴的三要件:終止、處理、和遞迴。

    void not_endless(int i){
    if(i>10) return; // 終止:什麼時候要結束
    printf("%d",i); // 處理:要做什麼
    not_endless(i+1); // 遞迴:怎麼進到下一個遞迴
    }
  5. 對 NULL 取值

    int *ptr = NULL;
    printf("%d",*ptr);

    或是

    typedef struct Node{
    int val;
    struct Node *next;
    }node;

    node *ptr = NULL;
    printf("%d",ptr->val);

    當然,應該是沒有人會指定完 NULL 之後就取值,但是有可能一個原本不是 NULL 的東西在程式執行的過程中變成 NULL 了(通常會在遞迴時發生),或是你忘記對這個變數賦值了。

TLE (time limit exceed)

不外乎就是兩種,無限迴圈跟無限遞迴(前者可能性比較大)。
檢查一下你的 while、for、遞迴的終止條件有沒有漏洞。
如果都沒問題的話,一個可能是你用了太麻煩的算法導致你的程式跑太久才會得到答案,這部分就要自己想怎麼優化你的程式了。

WA (wrong answer)

各種奇怪的原因都有可能會 WA,包括上面那些問題也有可能會 WA。
一種情況是你想錯了,你的解法本身就有問題。
另一種情況就是你的程式有 bug(廢話),這時候就是考驗 debug 實力的時候了。

怎麼debug

  1. 設斷點

    int main(){
    ...someting code
    printf("I'm OK here!");
    exit(0);
    ...someting code
    }
    int counter = 0;
    void loop(){
    ...someting code
    if(counter>5){
    printf("I'm OK here!");
    exit(0);
    }
    else counter++;
    ...someting code
    }

    如果你在 terminal 上看到了 I’m OK here! 就表示上半段的程式沒有問題,如果沒看到,就表示問題在上半段的程式,用這種方法可以很快找出問題的發生點。
    你也可以讓它 print 某些變數的值來確認你的程式的運行狀況是不是符合你的預期。

  2. 故意誘發錯誤訊息
    假設我現在因為不明原因一直有某個測資 WA,然後我猜測可能是發生了某個情況。

    int main(){
    ...someting code
    if(REcondition) exit(-1); // RE
    ...someting code
    }

    如果 condition 是 true,那麼就會變成 RE,這樣我就可以猜測我在這個情況下會不會 RE,當然你也可以製造 WA 或是 TLE 等等。

    int main(){
    ...someting code
    if(TLEcondition){ //TLE
    int a=0;
    while(true) a++;
    }
    if(WAcondition){ // WA
    printf("This must not be answer");
    exit(0);
    }
    ...someting code
    }

    但計程的考試有提交次數限制,要注意這點。