連線計分 題解|APCS 2026 年 11 月中級 第 2 題
簡潔題意
\(R \times C\) 的棋盤,\((r, c)\) 是第 \(r\) 列第 \(c\) 欄(都從 \(1\) 開始),每格是空格(\(0\))、黑棋(\(1\))或白棋(\(2\)),至少有一個空格。挑一個空格下一顆黑棋或白棋,看通過新棋子的橫、直、左上到右下、右上到左下四條線:每條線從新棋子往兩邊數連在一起的同色棋子,連同新棋子共 \(L\) 顆,\(L \ge 3\) 得 \(L^2\) 分;四條線的分數加起來是這一步的得分。輸出得分最高的位置 \(r\)、\(c\) 與分數;同分選列編號最小的,再選欄編號最小的——所有空格都是 \(0\) 分時,就是第一個空格與 \(0\)(\(1 \le R, C \le 50\))。
依正確通過的測試資料筆數給分,其中:
- 子題組 1(\(60\) 分):\(R = 1\)。
- 子題組 2(\(40\) 分):無額外限制。
先拿下子題組 1(60 分):只有一列,往左數、往右數
\(R = 1\) 時棋盤只有一列,直的和斜的線上只有新棋子自己(\(L = 1\)),不會得分——只要算橫的。
把這一列讀進陣列,欄號從 \(1\) 用起、多開一格(語法書 6.3)。對每個空格,黑棋、白棋各試一次:
- 從新棋子的左邊一格往左數、右邊一格往右數,各數連續幾顆這個顏色,碰到空格、另一種顏色或邊界就停;
- 兩邊的顆數加上新棋子自己就是 \(L\),\(L \ge 3\) 得 \(L^2\) 分。兩邊要加在一起算:
1 1 0 1 1在中間下黑棋是一條 \(5\) 顆的線,得 \(25\) 分,不是兩條 \(3\) 顆的線; - 黑、白兩種取大的,就是這一格的得分。
最高分 bestS 從 \(-1\) 開始——比所有可能的得分都小(語法書 4.8),由左到右掃,嚴格大於才換:
- 第一個空格至少 \(0\) 分,一定會被記下來,全部 \(0\) 分時答案就是它;初值設 \(0\) 的話,這時一次都不會換。
- 後面的格子同分換不掉它,同分時留下欄編號最小的。
#include <bits/stdc++.h>
using namespace std;
const int MAX_C = 50;
int a[MAX_C + 1]; // a[c]:第 c 欄,從 1 用起
int main() {
int R, C;
cin >> R >> C; // 子題組 1 保證 R = 1:棋盤只有一列
for (int c = 1; c <= C; c++) cin >> a[c];
int bestC = 0, bestS = -1; // 最高分從 -1 開始
for (int c = 1; c <= C; c++) {
if (a[c] != 0) continue; // 只能下在空格
int score = 0; // 這一格的得分:黑、白兩種取大的
for (int color = 1; color <= 2; color++) {
int L = 1; // 新棋子自己
for (int j = c - 1; j >= 1 && a[j] == color; j--) L++; // 往左數
for (int j = c + 1; j <= C && a[j] == color; j++) L++; // 往右數
if (L >= 3) score = max(score, L * L);
}
if (score > bestS) { // 嚴格大於才換:同分留下欄編號小的
bestS = score;
bestC = c;
}
}
cout << 1 << ' ' << bestC << ' ' << bestS << '\n'; // 只有一列,列編號一定是 1
return 0;
}
這份程式交上去,兩個範例都會 WA——它們的 \(R\) 不是 \(1\)。這是正常的,逐筆給分之下子題組 1 的 \(60\) 分穩穩到手;想自己驗,用自測表的第 1、2 列。
從 60 分到 100 分:四條線,用方向表往兩邊數
\(R\) 不是 \(1\) 之後,直的和兩條斜的線也可能得分。四條線都和子題組 1 的橫線一樣,是從新棋子往兩個相反的方向數:橫的往右、往左,直的往下、往上,左上到右下往右下、往左上,右上到左下往左下、往右上——八個方向兩兩一組。
方向表裡只要記每條線的一個方向 \((dr, dc)\):往右 \((0, 1)\)、往下 \((1, 0)\)、往右下 \((1, 1)\)、往左下 \((1, -1)\)(\(r\) 往下加、\(c\) 往右加)。另一個方向就是正負號整個反過來的 \((-dr, -dc)\),不用另外記。圖 1 中間的紅框是新棋子,實線是表裡記的方向,虛線是反過來的另一邊:
往一個方向數,就是一條射線:一格一格走,出界或不是這個顏色就停(出界先用 inside 判斷)。寫成函式 countRun,一條線的 \(L\) 就是新棋子 \(1\) 顆,加上往 \((dr, dc)\)、往 \((-dr, -dc)\) 各數到的顆數。
顏色要注意:同一種顏色先把四條線的分數加起來,黑、白兩種再取大的。一步只下一顆棋子,不能橫的照黑棋算、斜的照白棋算。
最高分照子題組 1 的方法,從 \(-1\) 開始、嚴格大於才換;外層迴圈跑列、內層跑欄,由上而下、由左而右掃,同分時自然先比列、再比欄。
#include <bits/stdc++.h>
using namespace std;
const int MAX_R = 50;
const int MAX_C = 50;
int R, C;
int g[MAX_R + 1][MAX_C + 1]; // g[r][c]:第 r 列第 c 欄,列、欄都從 1 用起;0 空格、1 黑棋、2 白棋(放全域,函式才看得到)
// 四條線各記一個方向:橫(往右)、直(往下)、左上到右下(往右下)、右上到左下(往左下)
// 另一邊是正負號反過來的 (-dr[d], -dc[d])
const int dr[4] = {0, 1, 1, 1};
const int dc[4] = {1, 0, 1, -1};
bool inside(int r, int c) {
return 1 <= r && r <= R && 1 <= c && c <= C;
}
// 從 (r, c) 往 (stepR, stepC) 一格一格走,數連續幾顆 color(不含 (r, c) 自己)
int countRun(int r, int c, int stepR, int stepC, int color) {
int cnt = 0;
r += stepR;
c += stepC;
while (inside(r, c) && g[r][c] == color) {
cnt++;
r += stepR;
c += stepC;
}
return cnt;
}
int main() {
cin >> R >> C;
for (int r = 1; r <= R; r++)
for (int c = 1; c <= C; c++)
cin >> g[r][c];
int bestR = 0, bestC = 0, bestS = -1; // 最高分從 -1 開始
for (int r = 1; r <= R; r++) { // 外層跑列、內層跑欄:同分先比列、再比欄
for (int c = 1; c <= C; c++) {
if (g[r][c] != 0) continue; // 只能下在空格
int score = 0; // 這一格的得分:黑、白兩種取大的
for (int color = 1; color <= 2; color++) {
int total = 0; // 下這個顏色:四條線的分數加起來
for (int d = 0; d < 4; d++) {
int L = 1 + countRun(r, c, dr[d], dc[d], color)
+ countRun(r, c, -dr[d], -dc[d], color);
if (L >= 3) total += L * L;
}
score = max(score, total);
}
if (score > bestS) { // 嚴格大於才換
bestS = score;
bestR = r;
bestC = c;
}
}
}
cout << bestR << ' ' << bestC << ' ' << bestS << '\n';
return 0;
}
每個空格、兩種顏色、八個方向,每個方向最多走 \(49\) 步,最多 \(2500 \times 2 \times 8 \times 49 \approx 2 \times 10^6\) 步,一下子就跑完。分數最多是四條線各 \(50\) 顆,不會超過 \(4 \times 50^2 = 10^4\),int 綽綽有餘(語法書 2.9)。\(R = 1\) 時直的、斜的 countRun 第一步就出界,所以這份程式在子題組 1 也對。
測過再交:範例只有方形棋盤,最高分也只有一格
兩個範例都是方形棋盤,最高分都只有一格、不是 \(0\) 分;答案用到的線只有橫、直、右上到左下,左上到右下沒有算進任何一個答案;子題組 1 的一列棋盤也沒有出現。自己補上這幾種:
| 輸入 | 正確輸出 | 這一筆在測什麼 |
|---|---|---|
1 7 / 0 1 1 0 1 1 1 |
1 4 36 |
只有一列。\((1, 4)\) 下黑棋,把左邊 \(2\) 顆、右邊 \(3\) 顆接成 \(6\) 顆;\((1, 1)\) 往右數到第 \(3\) 欄就要停,第 \(4\) 欄是空格 |
1 5 / 1 0 2 0 1 |
1 2 0 |
全盤 \(0\) 分:每個空格下黑下白都只連成 \(2\) 顆。\((1, 1)\) 有棋子,要印第一個空格 \((1, 2)\) |
4 3 / 1 2 0 / 0 1 0 / 0 1 0 / 0 0 0 |
3 3 9 |
不是方形。\((3, 3)\) 下黑棋左上到右下 \(3\) 顆、\((4, 2)\) 下黑棋直的 \(3\) 顆,都是 \(9\) 分,列編號小的 \((3, 3)\) 贏 |
| 用程式造:\(50 \times 50\) 全部黑棋,只有 \((25, 25)\) 是空格 | 25 25 9901 |
最大的棋盤、最高的分數:橫、直、左上到右下各 \(50\) 顆,右上到左下 \(49\) 顆 |
四筆都親眼看過正確,這題就穩了。
常犯錯誤
- 八個方向各自當一條線(同一條線的兩邊沒有接起來):兩個範例都過,自測表第 1 列印
1 4 25。 - 每條線各自挑黑白比較好的再加總:範例 2 的 \((1, 2)\) 下黑棋橫的 \(16\) 分、下白棋左上到右下 \(9\) 分,混著算變成 \(25\) 分,印
1 2 25。 - 往外數碰到空格沒停(只在碰到另一種顏色或出界時停):兩個範例都過,自測表第 1 列印
1 1 36。 - 方向表漏了左上到右下:兩個範例都過——它們的答案都沒用到這條線;自測表第 3 列印
4 2 9。 - 最高分的初值設 \(0\)(位置預設 \((0, 0)\) 或 \((1, 1)\)):兩個範例都過,自測表第 2 列印
0 0 0或1 1 0——\((1, 1)\) 上面有棋子。 - 同分沒照順序:兩個範例都過——它們的最高分都只有一格。更新寫成
>=會留下最後一格,自測表第 2 列印1 4 0;外層迴圈跑欄會先比欄,第 3 列印4 2 9。 - 沒跳過有棋子的格子:兩個範例都過,自測表第 3 列印
1 2 9——\((1, 2)\) 本來就是白棋。 - 第一行讀成 \(C\)、\(R\):兩個範例都過——它們都是方形,讀反也一樣;自測表第 3 列印
3 1 9。