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\) 就是全部加總。這一段的重點其實在第二行的輸出格式:
- 選出來的數字要留著(一維陣列
chosen[],上冊 6.7),因為要等 \(S\) 算完才知道誰整除它。 - 整除=
sum % chosen[i] == 0(上冊 2.6)。 - 「數字之間一個空白、最後沒有空白、一個都沒有印 \(-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 1 / 2 / 2 / 4 |
8 / 2 2 4 |
\(M = 1\)(子題組 1 版就能驗);選出的數字有重複,\(2\) 要印兩次——去重的程式印 2 4,兩個範例都抓不到 |
2 2 / 3 5 / 5 3 |
10 / 5 5 |
最大值一群在右、一群在左;兩群都選 \(5\)、都整除,印兩次 |
1 3 / 4 9 2 |
9 / 9 |
\(N = 1\):唯一選出的數一定整除自己,第二行不可能是 \(-1\) |
2 2 / 6 6 / 6 6 |
12 / 6 6 |
同一群裡兩個一樣大(擂台平手不換人也沒關係);兩群都是 \(6\) |
2 1 / 2 / 3 |
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 印
10/5 1;\(M = 1\) 時每群的best是 \(0\),sum % 0直接當掉。 - \(-1\) 不管有沒有印過數字都印:範例 1 印
6 1-1。