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 ANDORXOR(三行)
0 0 1 IMPOSSIBLE
0 9 0 AND
0 9 1 ORXOR(兩行)
4 0 0 AND
4 0 1 ORXOR(兩行)
4 9 0 XOR
4 9 1 ANDOR(兩行)

八種都對,這題就穩了。

拿到分之後,可以想想看:十二個 if 有沒有更簡潔的寫法?下面的提示和解法就是往這個方向走,想挑戰的再展開。

提示(往更簡潔的寫法想)
  • 十二個 if 裡,a == 0a != 0 這件事被重複判斷了很多次。能不能先判斷一次,把結果存起來?把兩個數各自變成一個真假值(3.1),後面就不用再管原本的數值了。
  • AND、OR 正好對應語法書 3.3 教過的兩個邏輯運算子。XOR(恰好一個為真)雖然沒有現成的運算子,但想想看:兩個真假值「恰好一真一假」,換句話說就是它們怎麼樣
更簡潔的解法

窮舉版把「數值是不是 \(0\)」藏在每個條件裡,所以每種運算都要寫四列。其實可以只判斷一次:先把 \(a\)、\(b\) 正規化成真值。題目表格定義的是「邏輯」運算——只看是否為 \(0\),所以 \(4\)、\(9\) 這種數都要當成「真」來算,不能拿數值本身去運算。

比較運算的結果本身就是一個 bool3.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)\)。

常犯錯誤
  1. 拿原本的數值直接運算或比較,或把條件寫成 a == 1:\(a\)、\(b\) 不一定是 \(0\) 或 \(1\)(可能是 \(4\)、\(9\)),要用「是否為 \(0\)」判斷真假,否則後面全錯。
  2. 窮舉版漏抄或抄錯真值表的某一列:十二個 if 少一個、多打一個 !,都只會錯在特定輸入上——這正是上面要你把八種輸入全部測過一遍的原因。
  3. 漏掉 IMPOSSIBLE:三個運算都不符合時要輸出 IMPOSSIBLE,不能什麼都不印。
  4. 輸出順序錯:固定依 AND、OR、XOR 的順序,一行一個。
  5. 大小寫錯誤:全部大寫,andOr 都不行。
  6. 直接寫 a & ba ^ 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;
}