题目描述
“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 16 16 16 个把手的冰箱。
已知每个把手可以处于以下两种状态之一:打开或关闭。
只有当所有把手都打开时,冰箱才会打开。
把手可以表示为一个 4 × 4 4×4 4×4 的矩阵,您可以改变任何一个位置 [ i , j ] [i,j] [i,j] 上把手的状态。
但是,这也会使得第 i i i 行和第 j j j 列上的所有把手的状态也随着改变。
请你求出打开冰箱所需的切换把手的次数最小值是多少。
输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号 +
表示把手处于闭合状态,而符号 -
表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。
输出格式
第一行输出一个整数 N N N,表示所需的最小切换把手次数。
接下来 N N N 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。
注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。
数据范围
1 ≤ i , j ≤ 4 1≤i,j≤4 1≤i,j≤4
输入样例
-+--
----
----
-+--
输出样例
6
1 1
1 3
1 4
4 1
4 3
4 4
题目分析
我们枚举一个 16 16 16 位二进制数来枚举切换把手的状态,对于每一个二进制数,我们将其最终的状态表示出来,判断是否满足所有把手都打开,如果满足且步数更少,则更新答案。
需要注意的是,题目要求按照优先级输出。其实我们二进制数枚举的方法已经满足了在相同步数下,先枚举到优先级更高的方案。我们发现,按顺序枚举时,当二进制数中的 1 1 1 个数相同时,总是先有最低边的全是 1 1 1,在向这些 1 1 1 中从高位至低位添 0 0 0。譬如,我们枚举有 4 4 4 位是 1 1 1 的 5 5 5 位二进制数时,我们的枚举顺序是 1111 → 10111 → 11011 → 11101 → 11110 1111→10111→11011→11101→11110 1111→10111→11011→11101→11110,总是优先级高的先被枚举到。
代码:
#include <iostream>
#include <cstring>
using namespace std;
char m[5][5];
int map[5][5], tmp[5][5], res = 1e9, plan;
int main(){
for (int i = 1; i <= 4; i ++)
cin >> m[i] + 1;
for (int i = 1; i <= 4; i ++)
for (int j = 1; j <= 4; j ++)
map[i][j] = m[i][j] == '+' ? 1 : 0;
for (int i = 1; i < 1 << 16; i ++){
bool success = 1;
int step = 0;
memcpy(tmp, map, sizeof(tmp));
for (int j = 0; j < 16; j ++){
if ((i >> j) & 1){
step ++;
int line = j / 4 + 1, column = j % 4 + 1;
for (int h = 1; h <= 4; h ++)
tmp[h][column] ^= 1, tmp[line][h] ^= 1;
tmp[line][column] ^= 1;
}
}
for (int h = 1; h <= 4; h ++)
for (int j = 1; j <= 4; j ++)
if (tmp[h][j]){
success = 0;
break;
}
if (success && res > step)
res = step, plan = i;
}
cout << res << endl;
for (int i = 0; i < 16; i ++)
if ((plan >> i) & 1){
int line = i / 4 + 1, column = i % 4 + 1;
cout << line << " " << column << endl;
}
return 0;
}