# APCS實作題2026年3月中高級第3題:校運代表隊
> 日期:2026年3月12日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=s181)
## 題目
### 問題描述
學校準備組建一支代表隊參加校運會。全校共有 $m$ 個班級,每個班級各派出 $r$ 位學生,因此總共有 $m \times r$ 位學生。學生們被統一編號,第 1 班的學生編號為 $1, 2, \dots, r$,第 2 班為 $r+1, r+2, \dots, 2r$,以此類推,第 $i$ 班的學生編號區間為 $[(i-1) \times r + 1, ir]$。
每位學生都擅長一種特定的比賽項目,共有 $n$ 種不同的項目。第 $i$ 位學生擅長的項目編號為 $a_i$。
校方希望從這 $m \times r$ 位學生中選出 $k$ 個人組成代表隊,並滿足以下嚴格條件:
1. **專長不重複**:這 $k$ 個人所擅長的項目必須互不相同。
2. **班級限額**:每個班級最多只能有 2 位學生被選入代表隊。
現在給定所有學生的專長項目以及整數 $t$,請找到所有合法組合中,學生編號組合字典序第 $t$ 小的方案。
<br />
### 輸入說明
第一行包含五個整數 $n, m, k, r, t$ ($1 \leq n \leq 9, 1 \leq m \leq 17, 1 \leq r \leq 4, 1 \leq k \leq 5$),$t$ 不超過合法組合數量。
第二行包含 $m \times r$ 個整數 $a_1, a_2, \dots, a_{m \times r}$ ($1 \leq a_i \leq n$),代表每位學生的專長。
配分
- 40分:$r = 1$
- 60分:無限制。
<br />
### 輸出格式
輸出一行包含 $k$ 個整數,代表第 $t$ 小組合中的學生編號。編號請按升序排列輸出。
<br />
### 範例輸入1
```
5 2 4 3 2
3 1 2 4 1 3 1 5
```
### 範例輸出1
```
1 3 5
```
### 範例輸入2
```
8 10 4 5 536
5 5 8 2 3 1 1 3 7 8 2 7 4 1 5 7 7 5 2 1 6 7 7 8 4 7 5 3 2 7 8 4 2 5 3 6 7 8 8 6
```
### 範例輸出2
```
1 3 7 29 32
```
<br />
## Python 程式碼
這題考 DFS 與窮舉,要加上一些剪枝條件加速,以下的程式還有改進的空間。費時最久約為 0.9 s,使用記憶體最多約為 3 MB,通過測試。
```python=
import sys
sys.setrecursionlimit(10000)
n, m, r, k, t = map(int, input().split())
skills = [0] + list(map(int, input().split()))
tot = m*r
cnt = [0]
def dfs(start, path, used_skill, class_cnt):
if len(path) == k:
cnt[0] += 1
if cnt[0] == t:
print(*path)
sys.exit(0) # 找到一組解就中止程式
return
for i in range(start, tot+1):
class_idx = (i-1)//r
if class_cnt[class_idx] >= 2 or used_skill[skills[i]]:
continue
class_cnt[class_idx] += 1
used_skill[skills[i]] = True
path.append(i)
dfs(i+1, path, used_skill, class_cnt)
class_cnt[class_idx] -= 1
used_skill[skills[i]] = False
path.pop()
dfs(1, [], [False]*(n+1), [0]*m)
```
<br />
費時最久約為 0.9 s,使用記憶體最多約為 3 MB,通過測試。
```python=
import sys
sys.setrecursionlimit(10000)
data = sys.stdin.read().split()
ptr = 0
n, m, r, k, t = map(int, data[ptr:ptr+5])
ptr += 5
tot = m*r
skills = [0] + list(map(int, data[ptr:ptr+tot]))
ptr += tot
cnt = [0]
def dfs(start, path, used_skill, class_cnt):
if len(path) == k:
cnt[0] += 1
if cnt[0] == t:
res = " ".join(map(str, path))
sys.stdout.write(f"{res}\n")
sys.exit(0) # 找到一組解就中止程式
return
for i in range(start, tot+1):
class_idx = (i-1)//r
if class_cnt[class_idx] >= 2 or used_skill[skills[i]]:
continue
class_cnt[class_idx] += 1
used_skill[skills[i]] = True
path.append(i)
dfs(i+1, path, used_skill, class_cnt)
class_cnt[class_idx] -= 1
used_skill[skills[i]] = False
path.pop()
dfs(1, [], [False]*(n+1), [0]*m)
```
<br />
不使用 sys.exit(0) 中止程式,費時最久約為 0.9 s,使用記憶體最多約為 3 MB,通過測試。
```python=
def solve():
import sys
sys.setrecursionlimit(10000)
data = sys.stdin.read().split()
ptr = 0
n, m, r, k, t = map(int, data[ptr:ptr+5])
ptr += 5
tot = m*r
skills = [0] + list(map(int, data[ptr:ptr+tot]))
ptr += tot
cnt = [0]
def dfs(start, path, used_skill, class_cnt, stop):
if stop:
return True
if len(path) == k:
cnt[0] += 1
if cnt[0] == t:
res = " ".join(map(str, path))
sys.stdout.write(f"{res}\n")
return True
return False
for i in range(start, tot+1):
class_idx = (i-1)//r
if class_cnt[class_idx] >= 2 or used_skill[skills[i]]:
continue
class_cnt[class_idx] += 1
used_skill[skills[i]] = True
path.append(i)
# 如果下層回傳 True,已經找到解,回傳 True
if dfs(i+1, path, used_skill, class_cnt, stop):
return True
class_cnt[class_idx] -= 1
used_skill[skills[i]] = False
path.pop()
return False # 這個分枝沒有找到解
dfs(1, [], [False]*(n+1), [0]*m, False )
if __name__ == "__main__":
solve()
```
<br /><br />
## C++ 程式碼
以下的程式還有改進的空間。如果沒有第20行的 **exit(0);** 費時最久約為 52 ms,使用記憶體最多約為 312 kB,通過測試;如果加上這行,找到一組解就中止程式,費時最久約為 47 ms,使用記憶體最多約為 308 kB,通過測試。
```cpp=
#include <cstdio>
#include <cstdlib> // for exit(0);
#include <vector>
#include <map>
using namespace std;
int n, m, r, k, t, tot, cnt;
vector<int> skills, class_cnt;
map<int, bool> used_skill;
void dfs(int start, vector<int>& path) {
if ((int)path.size() == k) {
cnt++;
if (cnt == t) {
for(int i=0; i<k; i++) {
printf("%d", path[i]);
if (i == k-1) printf("\n");
else printf(" ");
}
exit(0); // 找到一組解就中止程式,需要引入 cstdlib
}
return;
}
for(int i=start; i<=tot; i++) {
int class_idx = (i-1) / r;
if (class_cnt[class_idx] >= 2 || used_skill[skills[i]]) {
continue;
}
class_cnt[class_idx]++;
used_skill[skills[i]] = true;
path.push_back(i);
dfs(i+1, path);
class_cnt[class_idx]--;
used_skill[skills[i]] = false;
path.pop_back();
}
}
int main() {
scanf("%d %d %d %d %d", &n, &m, &r, &k, &t);
tot = m*r;
cnt = 0;
skills.resize(tot + 1);
class_cnt.assign(m, 0);
used_skill.clear();
for(int i=1; i<=tot; i++) {
scanf("%d", &skills[i]);
}
vector<int> path;
dfs(1, path);
return 0;
}
```
<br />
不使用 cstdlib 函式庫的 exit(0),改用回傳 bool 值的方式在找到一組解就停止程式,費時最久約為 43 ms,使用記憶體最多約為 312 kB,通過測試。
```cpp=
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
int n, m, r, k, t, tot, cnt;
vector<int> skills, class_cnt;
map<int, bool> used_skill;
bool dfs(int start, vector<int>& path, bool stop) {
if (stop) return true; // 已經找到解
if ((int)path.size() == k) {
cnt++;
if (cnt == t) {
for(int i=0; i<k; i++) {
printf("%d", path[i]);
if (i == k-1) printf("\n");
else printf(" ");
}
return true;
}
return false;
}
for(int i=start; i<=tot; i++) {
int class_idx = (i-1) / r;
if (class_cnt[class_idx] >= 2 || used_skill[skills[i]]) {
continue;
}
class_cnt[class_idx]++;
used_skill[skills[i]] = true;
path.push_back(i);
// 如果下層有找到解,回傳 true
if (dfs(i+1, path, stop)) {
return true;
}
class_cnt[class_idx]--;
used_skill[skills[i]] = false;
path.pop_back();
}
return false; // 這個分枝沒有找到解,回傳 false
}
int main() {
scanf("%d %d %d %d %d", &n, &m, &r, &k, &t);
tot = m*r;
cnt = 0;
skills.resize(tot + 1);
class_cnt.assign(m, 0);
used_skill.clear();
for(int i=1; i<=tot; i++) {
scanf("%d", &skills[i]);
}
vector<int> path;
bool stop = false;
dfs(1, path, stop);
return 0;
}
```
<br />
---
###### tags:`APCS`、`Python`、`C++`