“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有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
思路:飞行员兄弟这一题和费解的开关有相似的地方(费解的开关链接),其实都是按开关的问题,只不过,飞行员兄弟拉一个把手会影响所在行和所在列的全部把手的状态,而按开关的问题要记住一个很重要的特性:每个开关只能按一次,开关按的顺序无关紧要,对于飞行员兄弟这一题来说数据规模较小,所以我在这里采用的暴力破解的方法。
废话就不说了,上代码
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
typedef struct {
int x;
int y;
}plane;
const int N = 5;//最后一位存'\0'
char g[N][N], backup[N][N];//存开关布局
//改变单个把手的状态
void change(int x, int y) {//'-'变'+','+'变'-'
if (g[x][y] == '-') {
g[x][y] = '+';
}else {
g[x][y] = '-';
}
}
//改变对应行和列的状态
void turn(int x, int y) {
for (int i = 0; i < 4; i++) {
change(x, i);
change(i, y);
}
change(x, y);//特别注意,两次按按钮归原位,记得再按一次
}
int main() {
//初始化
for (int i = 0; i < 4; i++) {
cin >> g[i];
}
vector<plane>res;
memcpy(backup, g, sizeof(g));//备份
for (int start = 0; start < (1 << 16); start++) {//共有2^16-1种操作方案
vector < plane>temp;
for (int i = 0; i < 16; i++) {//检测哪个按钮给按过(把手给操作过)
if ((start >> i) & 1) {
int x = i / 4;
int y = i % 4;
plane p1 = { x,y };
temp.push_back(p1);
turn(x, y);
}
}
//判断把手是否都为打开着的
bool test = false;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (g[i][j] == '+') {
test = true;
break;
}
}
}
if (!test) {
if (res.empty() || res.size() > temp.size()) {
res = temp;
}
}
temp.clear();
memcpy(g, backup, sizeof(g));//还原
}
cout << res.size() << endl;
for (int i = 0; i < res.size(); i++) {
cout << res[i].x + 1 << " "
<< res[i].y + 1 << endl;//要记得是从(1,1)开始,而不是(0,0)
}
return 0;
}
运行结果:
总结:
对于这一题来说,数据规模较小所以可以进行暴力的方法来搞,对全部方案进行设置,然后再利用位操作定位每个操作了的按钮,对棋盘(把手的位置)进行改变,在棋盘的连带关系种要特别注意的一点就是在设置十字的连带关系中会给设置两次就会回归原位,所以要记得去再单独设置一次(一开始写就死在这上面了😂)
acwing的y总还是牛皮(๑•̀ㅂ•́)و✧,听y总的课总会学到一些骚操作