# APCS實作題2025年11月高級第1題:字母配對
> 第1版:2025年11月17日
> 第2版:2025年11月24日,更正標題,這題是 APCS **高級**實作題,不是中高級,我之前弄錯了。
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=r626)
<br />
## 解題想法
我一開如是用優先佇列儲存資料,內容為 **分數, 從每個堆疊讀取資料的索引值**,分數最高的資料放在最上面。由於 Python 的 heapq 是最小優先佇列,所以要把分數改成負值,儲存索引值的部分要用 tuple,並用一個 set 儲存已經測試過的狀態,避免重複計算同樣的狀態才不會超時。
後來又想到,這個題目要測試所有的移除方式找最高分,並不需要從當時的最高分組合往下找,可以用 BFS 就好。測試之後發現,這個寫法跟前一種寫法效率差不多。
<br /><br />
## Python 程式碼
heapq,使用時間約為 0.2 s,記憶體約為 8.3 MB,通過測試。
```python=
import heapq
m = int(input()) # m 個堆疊
piles = [[] for _ in range(m)] # 堆疊, (字母, 分數)
for i in range(m): # 讀取 m 行
parts = input().split()
n = int(parts[0]) # n 個物品
for j in range(1, 2*n+1, 2):
piles[i].append((parts[j], int(parts[j+1])))
initial_state = tuple([0]*m) # 初始狀態,要用 tuple
visited = {initial_state} # 已經檢查過的狀態
pq = [(0, initial_state)] # 優先佇列,(分數負值, 從每個堆疊讀取資料的索引值)
imax = 0 # 最高分數
while pq: # 如果 pq 有資料繼續執行
score, heads = heapq.heappop(pq) # 取出 pq 最上面的項目
score = -score # 分數換回正值
imax = max(imax, score) # 更新最高分數
for i in range(m-1): # 掃過第 0 ~ m-2 堆
if heads[i] >= len(piles[i]): continue # 已出界,找下一個
a = piles[i][heads[i]] # 物品 a
for j in range(i+1, m): # 掃過第 i+1 ~ m-1 堆
if heads[j] >= len(piles[j]): continue # 已出界,找下一個
b = piles[j][heads[j]] # 物品 b
if a[0] == b[0]: # 同字母
new_score = score + a[1] + b[1] # 新的分數
new_heads_list = list(heads) # 複製索引值
new_heads_list[i] += 1 # i 的索引值加 1
new_heads_list[j] += 1 # j 的索引值加 1
new_heads = tuple(new_heads_list) # 轉成 tuple
if new_heads not in visited: # 如果 new_heads 未走訪
visited.add(new_heads) # new_heads 加入 visited
heapq.heappush(pq, (-new_score, new_heads)) # 加入 pq
print(imax)
```
<br />
BFS,使用時間約為 0.2 s,記憶體約為 8.8 MB,通過測試。
```python=
from collections import deque
m = int(input()) # m 個堆疊
piles = [[] for _ in range(m)] # 堆疊, (字母, 分數)
for i in range(m): # 讀取 m 行
parts = input().split()
n = int(parts[0]) # n 個物品
for j in range(1, 2*n+1, 2):
piles[i].append((parts[j], int(parts[j+1])))
initial_state = tuple([0]*m) # 初始狀態,要用 tuple
visited = {initial_state} # 已經檢查過的狀態
que = deque([(0, initial_state)]) # 佇列,(分數, 從每個堆疊讀取資料的索引值)
imax = 0 # 最高分數
while que: # 如果 que 有資料繼續執行
score, heads = que.popleft() # 取出 que 最前面的項目
imax = max(imax, score) # 更新最高分數
for i in range(m-1): # 掃過第 0 ~ m-2 堆
if heads[i] >= len(piles[i]): continue # 已出界,找下一個
a = piles[i][heads[i]] # 物品 a
for j in range(i+1, m): # 掃過第 i+1 ~ m-1 堆
if heads[j] >= len(piles[j]): continue # 已出界,找下一個
b = piles[j][heads[j]] # 物品 b
if a[0] == b[0]: # 同字母
new_score = score + a[1] + b[1] # 新的分數
new_heads_list = list(heads) # 複製索引值
new_heads_list[i] += 1 # i 的索引值加 1
new_heads_list[j] += 1 # j 的索引值加 1
new_heads = tuple(new_heads_list) # 轉成 tuple
if new_heads not in visited: # 如果 new_heads 未走訪
visited.add(new_heads) # new_heads 加入 visited
que.append((new_score, new_heads)) # 加入 que
print(imax)
```
<br /><br />
## C++ 程式碼
priority_queue,使用時間約為 34 ms,記憶體約為 2.8 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
#include <queue> // for priority_queue
#include <utility> // for pair
#include <set>
#include <algorithm> // for max
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int m; cin >> m; // m 個堆疊
vector<vector<pair<char, int>>> piles (m); // 堆疊, (字母, 分數)
for(int i=0; i<m; i++) { // 讀取 m 行
int n; cin >> n; // n 個物品
for(int j=0; j<n; j++) {
char name;
int score;
cin >> name >> score;
piles[i].push_back(make_pair(name, score));
}
}
vector<int> initial_state (m, 0); // 初始狀態,要用 tuple
set<vector<int>> visited; // 已經檢查過的狀態
visited.insert(initial_state);
priority_queue<pair<int, vector<int>>> pq; // 最大優先佇列,(分數, 從每個堆疊讀取資料的索引值)
pq.push(make_pair(0, initial_state));
int imax = 0; // 最高分數
while(!pq.empty()) { // 如果 pq 有資料繼續執行
int score = pq.top().first; // 取出 pq 最上面的項目
auto heads = pq.top().second;
pq.pop();
imax = max(imax, score); // 更新最高分數
for(int i=0; i<m-1; i++) { // 掃過第 0 ~ m-2 堆
if (heads[i] >= (int)piles[i].size()) continue; // 已出界,找下一個
auto a = piles[i][heads[i]]; // 物品 a
for(int j=i+1; j<m; j++) { // 掃過第 i+1 ~ m-1 堆
if (heads[j] >= (int)piles[j].size()) continue; // 已出界,找下一個
auto b = piles[j][heads[j]]; // 物品 b
if (a.first == b.first) { // 同字母
int new_score = score + a.second + b.second; // 新的分數
auto new_heads = heads; // 複製索引值
new_heads[i]++; // i 的索引值加 1
new_heads[j]++; // j 的索引值加 1
if (visited.count(new_heads) == 0) { // 如果 new_heads 未走訪
visited.insert(new_heads); // new_heads 加入 visited
pq.push(make_pair(new_score, new_heads)); // 加入 pq
}
}
}
}
}
cout << imax << "\n";
return 0;
}
```
<br />
BFS,使用時間約為 37 ms,記憶體約為 2.9 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
#include <queue>
#include <utility> // for pair
#include <set>
#include <algorithm> // for max
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int m; cin >> m; // m 個堆疊
vector<vector<pair<char, int>>> piles (m); // 堆疊, (字母, 分數)
for(int i=0; i<m; i++) { // 讀取 m 行
int n; cin >> n; // n 個物品
for(int j=0; j<n; j++) {
char name;
int score;
cin >> name >> score;
piles[i].push_back(make_pair(name, score));
}
}
vector<int> initial_state (m, 0); // 初始狀態,要用 tuple
set<vector<int>> visited; // 已經檢查過的狀態
visited.insert(initial_state);
queue<pair<int, vector<int>>> que; // 待走訪佇列,(分數, 從每個堆疊讀取資料的索引值)
que.push(make_pair(0, initial_state));
int imax = 0; // 最高分數
while(!que.empty()) { // 如果 que 有資料繼續執行
int score = que.front().first; // 取出 que 最前面的項目
auto heads = que.front().second;
que.pop();
imax = max(imax, score); // 更新最高分數
for(int i=0; i<m-1; i++) { // 掃過第 0 ~ m-2 堆
if (heads[i] >= (int)piles[i].size()) continue; // 已出界,找下一個
auto a = piles[i][heads[i]]; // 物品 a
for(int j=i+1; j<m; j++) { // 掃過第 i+1 ~ m-1 堆
if (heads[j] >= (int)piles[j].size()) continue; // 已出界,找下一個
auto b = piles[j][heads[j]]; // 物品 b
if (a.first == b.first) { // 同字母
int new_score = score + a.second + b.second; // 新的分數
auto new_heads = heads; // 複製索引值
new_heads[i]++; // i 的索引值加 1
new_heads[j]++; // j 的索引值加 1
if (visited.count(new_heads) == 0) { // 如果 new_heads 未走訪
visited.insert(new_heads); // new_heads 加入 visited
que.push(make_pair(new_score, new_heads)); // 加入 pq
}
}
}
}
}
cout << imax << "\n";
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`C++`、`Python`