【Uva 題庫解題】C++ 個人解題筆記 - part3
===
[TOC]
本次題庫擷取自 CPE 2025/05/20 歷屆考題:https://cpe.mcu.edu.tw/cpe/test_data/2025-05-20
## 1. B2-sequence
Uva Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=2004
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=d123
該題屬於一顆星選集的範圍,具體就不再說明了,直接給 Code:
上傳 Uva 的時候須注意條件:
- B2-sequence 必須嚴格遞增
- 且 $b_i >= 1$
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, test_case = 1;
while (cin >> n){
bool is_B2 = true;
vector <int> num(n);
for (int i = 0; i < n; ++i){
cin >> num[i];
if (num[i] < 1){
is_B2 = false;
}
}
bool is_strictly_increasing = is_B2;
for (int i = 1; i < n; ++i){
if (num[i] <= num[i - 1]){
is_strictly_increasing = false;
break;
}
}
is_B2 = is_strictly_increasing;
set <int> sums;
if (is_B2){
for (int i = 0; i < n; ++i){
for (int j = i; j < n; ++j){
int sum = num[i] + num[j];
if (sums.count(sum) > 0){
is_B2 = false;
break;
}
sums.insert(sum);
}
if (!is_B2) break;
}
}
cout << "Case #" << test_case++ << ": " << (is_B2 ? "It is a B2-Sequence." : "It is not a B2-Sequence.") << "\n" << "\n";
}
return 0;
}
```
## 2. Error Correction
Uva Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=482
題目翻譯:
有個布林矩陣具有偶數性質(parity property),當矩陣中每一列(row)與每一行(column)的總和皆為偶數,換言之,每列與每行中設定為 1 的位元數必須是偶數。以下是一個具有偶數性質的 4 × 4 矩陣:
```
1 0 1 0
0 0 0 0
1 1 1 1
0 1 0 1
```
此矩陣各列的總和分別為 2、0、4 與 2,各行的總和分別為 2、2、2 與 2。
你的任務是編寫一個程式,讀入一個矩陣並檢查它是否具有偶數性質(parity property)。
如果矩陣已經符合條件,則輸出 OK。
如果不符合,則檢查是否能僅透過修改一個位元(0 變 1 或 1 變 0)來使矩陣符合偶數性質。如果可以,輸出 Change bit (i,j),其中 i 為列號,j 為行號。
如果無論如何都無法達成,則輸出 Corrupt。
**輸入格式**
- 輸入檔包含一個或多個測資。
- 每個測資的第一行包含一個整數 n(n < 100),表示矩陣的大小。
- 接下來的 n 行,每行有 n 個整數。矩陣中僅會出現整數 0 與 1。
- 當讀到 n = 0 時,輸入結束。
**輸出格式**
對輸入檔中的每個矩陣,輸出一行:
- 若矩陣已符合偶數性質,輸出 OK。
- 若修改一個位元即可符合,輸出 Change bit (i,j)。
- 否則輸出 Corrupt。
範例輸入:
```
4
1 0 1 0
0 0 0 0
1 1 1 1
0 1 0 1
4
1 0 1 0
0 0 1 0
1 1 1 1
0 1 0 1
4
1 0 1 0
0 1 1 0
1 1 1 1
0 1 0 1
0
```
範例輸出:
```
OK
Change bit (2,3)
Corrupt
```
解題思路:
除了矩陣外開兩個陣列 `rowSum` 跟 `colSum`,紀錄每列每行的總和,這樣可快速得知該列或行中有幾個 1,進而判斷奇偶性。
for 迴圈遍歷一次 rowSum 與 colSum 中的數字,找出總和為奇數的列和行,記錄其位置。
- 若所有列和行的總和都是偶數(錯誤列和行數均為 0),表示矩陣本身已經符合偶數性質,輸出 "OK"。
- 若只有一列與一行是奇數,只要將這交叉點的位元(該行該列的元素)做反轉(0 變 1,1 變 0)即可讓整個矩陣符合偶數性質(不用真的反轉,只要記住位置就好),輸出 "Change bit (i,j)"。此處 i 與 j 都是從 1 開始計數的列與行位置。
- 剩餘其他任意狀況(錯誤列或錯誤行超過 1 個),表示無法一次修改一個位元使矩陣符合條件,輸出 "Corrupt"。
範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
while (cin >> n, n != 0){
vector <vector <int>> mat(n, vector<int>(n));
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
cin >> mat[i][j];
vector<int> rowSum(n);
vector<int> colSum(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
rowSum[i] += mat[i][j];
colSum[j] += mat[i][j];
}
}
vector<int> rowErrs, colErrs;
for (int i = 0; i < n; ++i) {
if (rowSum[i] % 2 != 0) {
rowErrs.push_back(i);
}
if (colSum[i] % 2 != 0) {
colErrs.push_back(i);
}
}
if (rowErrs.empty() && colErrs.empty()) {
cout << "OK" << "\n";
} else if (rowErrs.size() == 1 && colErrs.size() == 1) {
cout << "Change bit (" << (rowErrs[0] + 1) << "," << (colErrs[0] + 1) << ")" << "\n";
} else {
cout << "Corrupt" << "\n";
}
}
return 0;
}
```
## 3. Lotto
Uva Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=382
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=c074
題目翻譯:
在德國樂透遊戲中,你需要從集合 $\{1, 2, \cdots, 49\}$ 中選擇 $6$ 個數字。一種常見但並不會提高中獎機率的玩法,是先從 $49$ 個數字中選出一個包含 $k$ 個元素的子集 $S(k > 6)$,然後只用 $S$ 內的數字來組成多組遊戲。例如,當 $k = 8$ 且 $S = \{1, 2, 3, 5, 8, 13, 21, 34\}$ 時,可以形成 28 種可能的遊戲組合: $[1, 2, 3, 5, 8, 13], [1, 2, 3, 5, 8, 21], [1, 2, 3, 5, 8, 34], [1, 2, 3, 5, 13, 21], \cdots, [3, 5, 8, 13, 21, 34]$ 。
你的任務是撰寫一支程式,讀取數字 $k$ 和集合 $S$ ,然後輸出所有只由 $S$ 內元素所組成的所有可能遊戲。
**輸入格式**:
輸入檔將包含一個或多組測資。每組測資為一行,由數個以空格分隔的整數組成:
- 行首的第一個整數是 $k (6 < k < 13)$ 。
- 接下來的 $k$ 個整數表示升序排列的集合 $S$ 。
- 當 $k = 0$ 時,輸入結束。
**輸出格式**:
對於每組測資,列印所有可能的遊戲,每行一組。
- 每組遊戲的數字必須以升序排列,且數字間以一個空格分隔。
- 所有遊戲本身必須以字典序排序,即先比較最小數字,再比較第二小,以此類推,如範例輸出所示。
- 測資之間必須以一個空白行分隔。
- 最後一組測資之後不可多印空白行。
**範例輸入**:
```
7 1 2 3 4 5 6 7
8 1 2 3 5 8 13 21 34
0
```
**範例輸出**:
```
1 2 3 4 5 6
1 2 3 4 5 7
1 2 3 4 6 7
1 2 3 5 6 7
1 2 4 5 6 7
1 3 4 5 6 7
2 3 4 5 6 7
1 2 3 5 8 13
1 2 3 5 8 21
1 2 3 5 8 34
1 2 3 5 13 21
1 2 3 5 13 34
1 2 3 5 21 34
1 2 3 8 13 21
1 2 3 8 13 34
1 2 3 8 21 34
1 2 3 13 21 34
1 2 5 8 13 21
1 2 5 8 13 34
1 2 5 8 21 34
1 2 5 13 21 34
1 2 8 13 21 34
1 3 5 8 13 21
1 3 5 8 13 34
1 3 5 8 21 34
1 3 5 13 21 34
1 3 8 13 21 34
1 5 8 13 21 34
2 3 5 8 13 21
2 3 5 8 13 34
2 3 5 8 21 34
2 3 5 13 21 34
2 3 8 13 21 34
2 5 8 13 21 34
3 5 8 13 21 34
```
解題思路:
本題使用 DFS + Backtracking 演算法解題。
然後從題目敘述稍微觀察一下,可以知道這是一個組合枚舉的題目,每個解都有 $C^k_6$ 種組合之可能性。
那 DFS 的部分如何實現呢?
在此之前我們先做個暫存陣列 comb,表示要輸出的組合。
然後我們從左到右,第一個元素開始做嘗試,嘗試丟入中(`comb.push_back(S[i])`),直到找出六個為止,如果發現不行就回到上一步,把它刪掉再來一次遞迴。
這邊不要直接寫 `for (int i = 0; i < k; ++i)` 我們多設一個變數叫 `start`,控制下一層遞迴的起點,以免重複或倒退選擇 → `for (int i = start; i < k; ++i)`。
迴圈內的遞迴函式中的 `start` 參數欄位,則是 `i + 1`,也確保不會出現重複的元素。
最後就可以設計出完整的一套 `dfs()` 啦!
```cpp=
void dfs(const vector<int>& S, int start, vector<int>& comb, vector<vector<int>>& result) {
if (comb.size() == 6) {
result.push_back(comb);
return;
}
for (int i = start; i < S.size(); ++i) {
comb.push_back(S[i]);
dfs(S, i + 1, comb, result);
comb.pop_back();
}
}
```
完整範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
void dfs(const vector<int>& S, int start, vector<int>& comb, vector<vector<int>>& result) {
if (comb.size() == 6) {
result.push_back(comb);
return;
}
for (int i = start; i < S.size(); ++i) {
comb.push_back(S[i]);
dfs(S, i + 1, comb, result);
comb.pop_back();
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int k;
bool first = true;
while (cin >> k && k != 0) {
vector<int> S(k);
for (int i = 0; i < k; ++i) cin >> S[i];
vector<vector<int>> result;
vector<int> comb;
dfs(S, 0, comb, result);
if (!first) cout << '\n';
first = false;
for (const auto& v : result) {
for (int i = 0; i < v.size(); ++i) {
if (i) cout << " ";
cout << v[i];
}
cout << '\n';
}
}
return 0;
}
```
## 4. Goldbach's Conjecture
Uva Judge:https://onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=484
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=c050
題目翻譯:
1742 年,德國業餘數學家 Christian Goldbach(哥德巴赫)寫信給 Leonhard Euler(歐拉),提出了以下猜想:每個大於 2 的數都可以寫成三個質數的和。哥德巴赫當時把 1 也算作質數,這個慣例現在已經不再採用。後來,歐拉將這個猜想重新表述為:每個大於或等於 4 的偶數都可以寫成兩個質數的和。例如:
- 8 = 3 + 5,3 和 5 都是奇質數。
- 20 = 3 + 17 = 7 + 13。
- 42 = 5 + 37 = 11 + 31 = 13 + 29 = 19 + 23。
至今仍未證明這個猜想是否正確。(哦等等,當然我有證明,只是它太長了,不能寫在這一頁的邊緣。)
不過,現在你的任務是以歐拉表述的方式,驗證所有小於一百萬的偶數是否符合哥德巴赫猜想。
輸入說明:
輸入檔案包含一個或多組測資。每組測資包含一個偶整數 $n$ ,且 $6 ≤ n < 1000000$ 。當 $n$ 為 $0$ 時,輸入結束。
輸出說明:
對每組測資,輸出一行,格式為 $n = a + b$ ,其中 $a$ 和 $b$ 是奇質數。數字和運算子之間要用一個空格分隔,格式請參考以下範例輸出。如果有多組奇質數配對使其和為 $n$,請選擇差值 $b − a$ 最大的組合。如果沒有符合條件的組合,則輸出 "Goldbach's conjecture is wrong."
範例輸入:
```
8
20
42
0
```
範例輸出:
```
8 = 3 + 5
20 = 3 + 17
42 = 5 + 37
```
解題思路:
使用質數篩法,先建立到 1000000 的質數表,再來我們把奇數質數存起來(2 不用考慮,題目的輸出都要奇質數)。
再來找出對每個輸入的偶數 $n$ ,差值最大( $b - a$ )的一組奇質數 $a, b$ ,符合 $n = a + b$ 的輸出格式。
範例程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
const int MAX_N = 1000000;
bool is_prime[MAX_N];
vector <int> odd_primes;
void sieve(){
fill(is_prime, is_prime + MAX_N + 1, true);
is_prime[0] = false;
is_prime[1] = false;
for (int i = 2; i * i <= MAX_N; ++i){
if (is_prime[i]){
for (int j = i * i; j <= MAX_N; j += i)
is_prime[j] = false;
}
}
for (int i = 3; i <= MAX_N; i += 2){
if (is_prime[i]) odd_primes.push_back(i);
}
}
pii find_goldbach_pair(int n){
for (int a : odd_primes){
if (a > n / 2) break;
int b = n - a;
if (is_prime[b]){
return {a, b};
}
}
return {-1, -1};
}
int main(){
ios::sync_with_stdio(false), cin.tie(nullptr);
sieve();
int n;
while (cin >> n && n != 0){
pii ans = find_goldbach_pair(n);
if (ans.first == -1)
cout << "Goldbach's conjecture is wrong." << "\n";
else
cout << n << " = " << ans.first << " + " << ans.second << "\n";
}
return 0;
}
```