Editorial for 最大和 (APCS 2016-10 中級)


簡潔題意

\(N\) 群數字、每群 \(M\) 個正整數(\(1 \le N, M \le 20\)、\(1 \le x_i \le 256\))。每群各選一個數字,讓總和 \(S\) 最大——也就是每群取最大值相加。第一行輸出 \(S\);第二行依群的順序輸出「被選出的數字裡能整除 \(S\) 的那些」,數字間一個空白、最後沒有空白,一個都沒有就輸出 \(-1\)。

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

  • 子題組 1(\(20\) 分):\(M = 1\)。
  • 子題組 2(\(30\) 分):\(M = 2\)。
  • 子題組 3(\(50\) 分):無額外限制。

先拿下子題組 1(20 分):每群只有一個數,選的就是它

\(M = 1\) 時「每群選一個」沒得選:第 \(i\) 群選出的就是讀進來的那個數,\(S\) 就是全部加總。這一段的重點其實在第二行的輸出格式

  1. 選出來的數字要留著(一維陣列 chosen[],上冊 6.7),因為要等 \(S\) 算完才知道誰整除它。
  2. 整除=sum % chosen[i] == 0(上冊 2.6)。
  3. 「數字之間一個空白、最後沒有空白、一個都沒有印 \(-1\)」三件事用一個 bool 旗標 printed 搞定:印每個數字之前,如果前面已經印過就先補一個空白;全部掃完 printed 還是 false 就印 \(-1\)。

同一個數字在不同群被選到就要印幾次(它們是不同群的代表),不要去重。

#include <iostream>
using namespace std;

const int MAX_N = 20;

int main() {
    int n, m;
    cin >> n >> m;                 // 子題組 1 保證 m = 1:每群只有一個數,選的就是它
    int chosen[MAX_N];             // 每一群選出來的數字
    int sum = 0;
    for (int i = 0; i < n; i++) {
        cin >> chosen[i];
        sum += chosen[i];
    }

    cout << sum << '\n';
    bool printed = false;          // 第二行印過數字了嗎?決定要不要先印空白、最後要不要印 -1
    for (int i = 0; i < n; i++) {
        if (sum % chosen[i] == 0) {
            if (printed) cout << ' ';
            cout << chosen[i];
            printed = true;
        }
    }
    if (!printed) cout << -1;
    cout << '\n';
    return 0;
}

這份程式交上去,兩個範例(\(M = 2\)、\(M = 3\))和子題組 2、3 大多會 WA——這是正常的,逐筆給分之下子題組 1 的 \(20\) 分穩穩到手;想自己驗,用自測表的第 1、5 列。

從 20 分到 100 分:每群打一次擂台

\(M\) 不是 \(1\) 之後,每群要選的是最大值(要讓總和最大,每群當然各取最大)——讀成 \(N \times M\) 的表格(13.1 的巢狀 vector),外層走「哪一群」、內層走「那一群的哪一格」(上冊 6.9 的二維遍歷)。每一群打一次擂台(上冊 3.9):擂主初值是這一群的第一個數 a[i][0],再讓第 \(1 \sim M - 1\) 格輪流挑戰,best = max(best, a[i][j])(上冊 7.6)。擂主初值設 \(0\) 再從第 \(1\) 格開始挑戰,第一格就永遠沒上過台。子題組 1 那段留下選出的數、旗標印第二行的寫法原封不動。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> a(n, vector<int>(m));   // n 列 m 行的表格
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];

    vector<int> chosen(n);   // 每一列選出來的數字
    int sum = 0;
    for (int i = 0; i < n; i++) {
        int best = a[i][0];
        for (int j = 1; j < m; j++)          // 掃過第 i 列的每一格
            best = max(best, a[i][j]);
        chosen[i] = best;
        sum += best;
    }

    cout << sum << '\n';
    bool printed = false;
    for (int i = 0; i < n; i++) {
        if (sum % chosen[i] == 0) {
            if (printed) cout << ' ';
            cout << chosen[i];
            printed = true;
        }
    }
    if (!printed) cout << -1;
    cout << '\n';
    return 0;
}

\(M = 2\) 的子題組 2 不需要特別處理:兩個數打擂台就是一次 max。運算量:\(20 \times 20\) 格,小到不用算;總和最大 \(20 \times 256 = 5120\)。

測過再交:兩個範例選出的數字都沒有重複

範例 1 的第二行有兩個數、範例 2 是 \(-1\),該有的長相都有了;但兩個範例選出的數字都互不相同,「同一個數字在不同群都被選到」要不要重複印,範例沒幫你驗。

輸入 正確輸出 這一筆在測什麼
3 1224 82 2 4 \(M = 1\)(子題組 1 版就能驗);選出的數字有重複,\(2\) 要印兩次——去重的程式印 2 4,兩個範例都抓不到
2 23 55 3 105 5 最大值一群在右、一群在左;兩群都選 \(5\)、都整除,印兩次
1 34 9 2 99 \(N = 1\):唯一選出的數一定整除自己,第二行不可能是 \(-1\)
2 26 66 6 126 6 同一群裡兩個一樣大(擂台平手不換人也沒關係);兩群都是 \(6\)
2 123 5-1 \(M = 1\) 也會印 \(-1\):子題組 1 版就能驗

五筆都親眼看過正確,這題就穩了。

常犯錯誤
  • 同一個數字只印一次(去重):兩個範例全過;自測表第 1 列印 2 4(該印 2 2 4)。
  • 印了第一個整除的就停:範例 1 印 6(該印 6 1)。
  • 把整張表裡能整除 \(S\) 的數字都印出來:只看「被選出的」——範例 1 印 1 6 4 1 1
  • \(S\) 把整群全部加總:範例 1 印 18
  • 擂主初值設 \(0\)、挑戰從第 \(1\) 格開始:每群第一個數永遠沒上台——範例 1 印 105 1;\(M = 1\) 時每群的 best 是 \(0\),sum % 0 直接當掉。
  • \(-1\) 不管有沒有印過數字都印:範例 1 印 6 1-1