《演算法競賽進階指南》0x00習題---飛行員兄弟題解
阿新 • • 發佈:2022-03-10
題目描述
“飛行員兄弟”這個遊戲,需要玩家順利的開啟一個擁有 16 個把手的冰箱。
已知每個把手可以處於以下兩種狀態之一:開啟或關閉。
只有當所有把手都開啟時,冰箱才會開啟。
把手可以表示為一個 \(4×4\) 的矩陣,您可以改變任何一個位置 \([i,j]\) 上把手的狀態。
但是,這也會使得第 \(i\) 行和第 \(j\) 列上的所有把手的狀態也隨著改變。
請你求出開啟冰箱所需的切換把手的次數最小值是多少。
輸入格式
輸入一共包含四行,每行包含四個把手的初始狀態。
符號 + 表示把手處於閉合狀態,而符號 - 表示把手處於開啟狀態。
至少一個手柄的初始狀態是關閉的。
輸出格式
第一行輸出一個整數 \(N\)
接下來 \(N\) 行描述切換順序,每行輸出兩個整數,代表被切換狀態的把手的行號和列號,數字之間用空格隔開。
注意:如果存在多種開啟冰箱的方式,則按照優先順序整體從上到下,同行從左到右開啟。
資料範圍
\(1≤i,j≤4\)
樣例
輸入樣例:
-+--
----
----
-+--
輸出樣例:
6
1 1
1 3
1 4
4 1
4 3
4 4
思路
- 二進位制列舉對 \(16\) 個位置的操作,低位 從上到下,從左到右 到高位。
- 同樣將初始狀態
init
對映成二進位制形式如樣例0010000000000010
右邊是最低位。 - 將行列操作對映成對一個數進行異或。比如第一行便是
^= 0000000000001111
- 再比如對第一列相當於
^=0001000100010001
,對第二列操作就是將第一列操作左移 \(1\) 位 - 注意同時對行列操作需要將交叉的部分再異或一下即
^=(row[x] & col[y])
- 至此記錄更新的答案和操作,再將數還原為行列座標即可
時間複雜度
\(O(2^{16} * 16)\)
C++ 程式碼
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 5, M = 16; int ans, init, op; // + 代表 1, - 代表 0, 每一行下移要 << 4, 每一列右移就 << 1 int row[4] = {15, 15 << 4, 15 << 8, 15 << 12}, col[4] = {4369, 4369 << 1, 4369 << 2, 4369 << 3}; int gen(int x, int y){ // 對第 x 行、第 y 列進行操作 return row[x] ^ col[y] ^ (row[x] & col[y]); } void change(int st){ int t = init, tmp = st, res = 0, cnt = 0; while(tmp){ if(tmp & 1){ int x = cnt / 4, y = cnt % 4; t = t ^ gen(x, y); res ++; } tmp >>= 1; cnt ++; } if(!t && res < ans){ // 更新答案 op = st; ans = res; } } int main(){ int cnt = 0; ans = 1000; for(int i = 0; i < 4; i++){ string s; cin >> s; for(int j = 0 ; j < s.size(); j++) if(s[j] == '+') init += 1 << (i * 4 + j); } for(int i = 0; i < 1 << M; i++) // 二進位制列舉操作方案 change(i); cnt = 0; cout << ans << endl; while(op){ if(op & 1) cout << cnt / 4 + 1 << " " << cnt % 4 + 1 << endl; op >>= 1; cnt ++; } return 0; }