Editorial for 程式考試 (APCS 2023-01 初級)


簡潔題意

\(K\) 筆提交紀錄,第 \(i\) 筆有時間 \(t_i\) 與分數 \(s_i\),\(s_i = -1\) 代表嚴重錯誤。總分 \(=\) 最高分 \(-\) 總提交次數 \(-\) 嚴重錯誤次數 \(\times 2\),算出來是負的就以 \(0\) 計。輸出總分,以及第一次拿到最高分的時間(\(1 \le K \le 6\)、\(-1 \le s_i \le 100\);時間嚴格遞增,第一筆保證不是嚴重錯誤)。

依正確通過的測試資料筆數給分,其中:

  • 子題組 1(\(60\) 分):\(K = 3\)。
  • 子題組 2(\(40\) 分):無額外限制。

先拿下子題組 1(\(60\) 分):三筆紀錄,if 就寫得完

\(K = 3\) 時把三筆紀錄讀成六個變數就好,連迴圈都不用——只用到語法書上冊第三單元為止的語法。

找最高分用擂台:題目保證第一筆不是嚴重錯誤,就讓第一筆直接站上擂台(best = s1bestTime = t1),後兩筆輪流上來挑戰。這裡有一個全題最關鍵的字:挑戰條件用 >,不能用 >=。題目要的是「第一次拿到最高分的時間」——同分的挑戰者不換人,擂台上就永遠站著最早的那一次;寫成 >= 就變成記到最後一次了。

#include <iostream>
using namespace std;

int main() {
    int k;                      // 子題組 1 保證 k = 3
    int t1, s1, t2, s2, t3, s3;
    cin >> k;
    cin >> t1 >> s1 >> t2 >> s2 >> t3 >> s3;

    int best = s1, bestTime = t1;   // 第一筆保證不是嚴重錯誤,直接站上擂台
    if (s2 > best) {                // 用 > 不用 >=:同分時保留較早那次
        best = s2;
        bestTime = t2;
    }
    if (s3 > best) {
        best = s3;
        bestTime = t3;
    }

    int errors = 0;                 // 嚴重錯誤次數
    if (s2 == -1) errors += 1;
    if (s3 == -1) errors += 1;

    int score = best - 3 - errors * 2;
    if (score < 0) score = 0;       // 負分以 0 計
    cout << score << " " << bestTime << endl;
    return 0;
}

範例 1 的 \(K = 5\),這支程式讀不完,答案會是錯的——這是正常的:APCS 逐筆給分,這一版穩穩拿下子題組 1 的 \(60\) 分(範例 2 剛好是 \(K = 3\),會過)。

從 \(60\) 分到 \(100\) 分:把六個變數換成一個迴圈

\(K\) 筆紀錄要做的事完全一樣:上擂台比一次、是 \(-1\) 就把嚴重錯誤加一。改用第四單元的單層 for,每輪讀一筆、當場處理掉,連存都不用存——不需要陣列。

改用迴圈之後,「讓第一筆先站上擂台」改用初值處理:分數最低是 \(-1\),把 best 的初值設成值域外的 \(-2\),第一筆不管是幾分都一定打得贏它。(要不要擔心 \(-1\) 反過來霸佔擂台?不用——題目保證第一筆不是嚴重錯誤,第一輪過後擂台上就站著一個 \(\ge 0\) 的分數,\(-1\) 永遠打不贏。)

#include <iostream>
using namespace std;

int main() {
    int k;
    cin >> k;

    int best = -2, bestTime = 0;    // 分數最低是 -1,-2 一定會被第一筆換掉
    int errors = 0;
    for (int i = 1; i <= k; i++) {
        int t, s;
        cin >> t >> s;
        if (s > best) {             // 用 > 不用 >=:同分時保留較早那次
            best = s;
            bestTime = t;
        }
        if (s == -1) {
            errors += 1;
        }
    }

    int score = best - k - errors * 2;
    if (score < 0) score = 0;       // 負分以 0 計
    cout << score << " " << bestTime << endl;
    return 0;
}

測過再交:範例沒考 \(K = 1\) 和「全部同分」

範例 1 考了「最高分出現兩次」(時間要取 \(10\) 不是 \(20\))、範例 2 考了負分歸零。還缺這幾種:

輸入 正確輸出 在測什麼
17 100 99 7 \(K = 1\):只有一筆,也要扣那一次提交
41 502 503 504 50 46 1 全部同分:時間要取最早的第 \(1\) 筆
61 82 -13 54 35 26 1 0 1 \(8 - 6 - 2\) 剛好等於 \(0\):不是負的,不該被動到

三個都親眼看過正確,這題就穩了。

常犯錯誤
  1. 擂台寫成 >=:同分時換成最後一次,「第一次拿到最高分」就錯了。範例 1 當場現形(印 83 20,正確是 83 10)。
  2. 忘了負分歸零:範例 2 當場現形(印 -7 3,正確是 0 3)。
  3. 嚴重錯誤只扣一倍:公式是扣 \(2\) 分一次。範例 1 當場現形(印 84 10)。
  4. best 初值設 \(0\):\(0\) 是合法的分數——最高分就是 \(0\) 時,擂台從頭到尾沒人上去過,bestTime 停在沒意義的初值。範例 2 當場現形(印 0 0,正確是 0 3)。