Editorial for 邏輯運算子 (APCS 2017-10 初級)
簡潔題意
給非負整數 \(a\)、\(b\)(皆 \(< 10000\))和一個結果 \(c\)(\(0\) 或 \(1\)),把 \(a\)、\(b\) 視為真值(\(0\) 為假、非 \(0\) 為真),依序檢查 AND、OR、XOR 三種邏輯運算的結果是否等於 \(c\),把符合的依序輸出,一行一個;都不符合輸出 IMPOSSIBLE。
依正確通過的測試資料筆數給分,其中:
- 子題組 1(\(80\) 分):\(a\)、\(b\) 的值只會是 \(0\) 或 \(1\)。
- 子題組 2(\(20\) 分):\(0 \le a, b < 10000\)。
先求做出來:照著真值表逐列翻譯
這一題不需要任何巧思:題目已經把 AND、OR、XOR 的真值表整張給你了,照抄成 if 就好。用三個 bool 旗標(3.5)分別記「答案可不可能是 AND/OR/XOR」,每種運算把表上的四列各翻成一個 if。唯一要小心的是:題目做的是「邏輯」運算,\(4\)、\(9\) 這種數也要當成「真」,所以條件要寫 a != 0,不能寫 a == 1。
以 OR 為例,真值表的四列翻出來就是:
bool is_or = false;
if (a == 0 && b == 0 && res == 0) is_or = true;
if (a == 0 && b != 0 && res == 1) is_or = true;
if (a != 0 && b == 0 && res == 1) is_or = true;
if (a != 0 && b != 0 && res == 1) is_or = true;
AND 和 XOR 也照樣各寫四個 if,總共十二個。完整程式:
#include <iostream>
using namespace std;
int main() {
int a, b, res;
cin >> a >> b >> res;
bool is_and = false;
if (a == 0 && b == 0 && res == 0) is_and = true;
if (a == 0 && b != 0 && res == 0) is_and = true;
if (a != 0 && b == 0 && res == 0) is_and = true;
if (a != 0 && b != 0 && res == 1) is_and = true;
bool is_or = false;
if (a == 0 && b == 0 && res == 0) is_or = true;
if (a == 0 && b != 0 && res == 1) is_or = true;
if (a != 0 && b == 0 && res == 1) is_or = true;
if (a != 0 && b != 0 && res == 1) is_or = true;
bool is_xor = false;
if (a == 0 && b == 0 && res == 0) is_xor = true;
if (a == 0 && b != 0 && res == 1) is_xor = true;
if (a != 0 && b == 0 && res == 1) is_xor = true;
if (a != 0 && b != 0 && res == 0) is_xor = true;
if (is_and) cout << "AND" << endl;
if (is_or) cout << "OR" << endl;
if (is_xor) cout << "XOR" << endl;
if (!is_and && !is_or && !is_xor) cout << "IMPOSSIBLE" << endl;
return 0;
}
這種寫法一點都不聰明,但它很穩——每一行都對應真值表的一列,寫錯了也很容易找到是哪一列。不要怕麻煩:就算要花一個小時才把它寫完、測完,也非常值得,畢竟 APCS 初級做對一題半就有二級分,把眼前這一題確實拿下來,比想出漂亮解法重要得多。
寫完先不要急著提交。這題的輸入其實只有 \(2 \times 2 \times 2 = 8\) 種長相(\(a\) 是否為 \(0\)、\(b\) 是否為 \(0\)、結果是 \(0\) 還是 \(1\)),八種全部自己餵一遍、對照真值表核對輸出。非 \(0\) 的位置故意用 \(4\)、\(9\) 這種數(不要只用 \(1\)),才測得出「非 \(0\) 就是真」有沒有寫對:
| 輸入 | 正確輸出 |
|---|---|
0 0 0 |
AND、OR、XOR(三行) |
0 0 1 |
IMPOSSIBLE |
0 9 0 |
AND |
0 9 1 |
OR、XOR(兩行) |
4 0 0 |
AND |
4 0 1 |
OR、XOR(兩行) |
4 9 0 |
XOR |
4 9 1 |
AND、OR(兩行) |
八種都對,這題就穩了。
拿到分之後,可以想想看:十二個 if 有沒有更簡潔的寫法?下面的提示和解法就是往這個方向走,想挑戰的再展開。
提示(往更簡潔的寫法想)
更簡潔的解法
窮舉版把「數值是不是 \(0\)」藏在每個條件裡,所以每種運算都要寫四列。其實可以只判斷一次:先把 \(a\)、\(b\) 正規化成真值。題目表格定義的是「邏輯」運算——只看是否為 \(0\),所以 \(4\)、\(9\) 這種數都要當成「真」來算,不能拿數值本身去運算。
比較運算的結果本身就是一個 bool(3.2),直接存進 bool 變數即可:
bool x = (a != 0); // 非 0 就是真
bool y = (b != 0);
bool target = (c == 1); // c 也轉成真假
轉完之後,三種運算各自對應:
- AND:
x && y——3.3 的「且」。 - OR:
x || y——3.3 的「或」。 - XOR:
x != y——這是整份解法的巧思。XOR 的定義是「恰好一個為真」,對兩個真假值來說就是「兩者不相等」,所以不需要任何新的運算子,3.2 的!=就是 XOR。
把三個結果逐一和 target 比較,符合就輸出該運算的名字,並用一個 bool 旗標記錄「至少輸出過一個」;三個都不符合,最後才輸出 IMPOSSIBLE。原本的十二個 if 就縮成三個。
順帶一提,整理真值表可以發現:唯一會輸出 IMPOSSIBLE 的情況是 \(a = b = 0\) 且 \(c = 1\)(三種運算在兩個都是假的時候結果都是 \(0\))。
時間複雜度 \(O(1)\)。
常犯錯誤
- 拿原本的數值直接運算或比較,或把條件寫成
a == 1:\(a\)、\(b\) 不一定是 \(0\) 或 \(1\)(可能是 \(4\)、\(9\)),要用「是否為 \(0\)」判斷真假,否則後面全錯。 - 窮舉版漏抄或抄錯真值表的某一列:十二個
if少一個、多打一個!,都只會錯在特定輸入上——這正是上面要你把八種輸入全部測過一遍的原因。 - 漏掉 IMPOSSIBLE:三個運算都不符合時要輸出
IMPOSSIBLE,不能什麼都不印。 - 輸出順序錯:固定依 AND、OR、XOR 的順序,一行一個。
- 大小寫錯誤:全部大寫,
and、Or都不行。 - 直接寫
a & b、a ^ b:單一個&、^是位元運算,對每個二進位位元分別運算——例如 \(4\) AND \(9\) 的邏輯結果是 \(1\),但4 & 9算出來是 \(0\)(\(4 = 100_2\)、\(9 = 1001_2\) 沒有共同的位元)。上冊 3.3 特別提醒過:單一個&、|和&&、||意思完全不同,而且編譯常常照樣過,錯得很隱蔽。
參考程式碼(簡潔版)
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
bool x = (a != 0); // 非 0 就是真
bool y = (b != 0);
bool target = (c == 1); // c 也轉成真假
bool found = false;
if ((x && y) == target) {
cout << "AND" << endl;
found = true;
}
if ((x || y) == target) {
cout << "OR" << endl;
found = true;
}
if ((x != y) == target) {
cout << "XOR" << endl;
found = true;
}
if (!found) {
cout << "IMPOSSIBLE" << endl;
}
return 0;
}