目录
116. 飞行员兄弟--枚举-位运算
“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 1616 个把手的冰箱。
已知每个把手可以处于以下两种状态之一:打开或关闭。
只有当所有把手都打开时,冰箱才会打开。
把手可以表示为一个 4×44×4 的矩阵,您可以改变任何一个位置 [i,j][i,j] 上把手的状态。
但是,这也会使得第 ii 行和第 jj 列上的所有把手的状态也随着改变。
请你求出打开冰箱所需的切换把手的次数最小值是多少。
输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号 +
表示把手处于闭合状态,而符号 -
表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。
输出格式
第一行输出一个整数 NN,表示所需的最小切换把手次数。
接下来 NN 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。
注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。
数据范围
1≤i,j≤41≤i,j≤4
输入样例:
-+--
----
----
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4
难度:简单 |
时/空限制:1s / 64MB |
来源: |
算法标签 |
分析:
代码:
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=5; //字符串后面有 \0 防止越界 多开一个位置
char g[N][N],backup[N][N];
//求x行, y列是多少 (从0开始)
int get(int x,int y){
return x*4+y;
}
void turn_one(int x,int y){
if(g[x][y]=='+') g[x][y]='-';
else g[x][y]='+';
}
void turn_all(int x,int y){
for(int i=0;i<4;i++){
turn_one(x,i); //例如点(2,2)先按(行)横着来turn:第二行第一个、第二行第二个、第二行第三个、第二行第四个。
turn_one(i,y); //然后(列)竖着来 turn :第一行第二个、第二行第二个、第三行第二个、第四行第二个。
}
turn_one(x,y); //因为点(x,y)turn了两遍,变回原样了,再turn一遍后即可
}
int main(){
for(int i=0;i<4;i++) cin>>g[i];
vector<PII> res;
for(int op=0;op<1<<16;op++){ //位运算:1<<16 :2的16次方
vector<PII> temp;
memcpy(backup,g,sizeof g); //备份
//进行操作
for(int i=0;i<4;i++){
for(int j=0;j<4;j++)
if(op>>get(i,j)&1){ //看一下op的二进制里面,get(i,j)位是否为 1,而get(i,j)的数为:0~15;同理,想看i的第k位是不是为1:i>>k&1
temp.push_back({i,j});
turn_all(i,j);
}
}
//判断所有灯泡是否全亮
bool has_closed=false;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++){
if(g[i][j]=='+'){
has_closed=true;
}
}
if(has_closed==false){
if(res.empty()||res.size()>temp.size()){
res=temp;
}
}
memcpy(g,backup,sizeof g); //还原
}
cout<<res.size()<<endl;
for(auto op:res){
cout<<op.x+1<<" "<<op.y+1<<endl;
}
return 0;
}
-
char g[N][N],backup[N][N];
:定义了两个二维字符数组,g用来存储当前状态的灯泡布局,backup用来备份灯泡布局。 -
int get(int x,int y)
:定义了一个函数get,用来计算在4x4的矩阵中,给定行和列的位置所对应的编号。 -
void turn_one(int x,int y)
和void turn_all(int x,int y)
:定义了两个函数,用来实现灯泡的按压操作。turn_one函数实现单个灯泡的按压,即将'+'变为'-',或者将'-'变为'+';turn_all函数实现在给定位置的灯泡按压后,将该位置所在行和列的所有灯泡进行按压。
4. op>>get(i,j)&1
是用来判断在当前的操作 op
中,第 (i,j)
个灯泡是否被按下的。让我们来分解这个表达式:
-
get(i,j)
:这个函数是用来计算在4x4的矩阵中,给定行和列的位置所对应的编号。根据代码中的定义,它把(i,j)
映射到一个0到15之间的唯一整数。 -
op>>get(i,j)
:这是对整数op
进行右移操作,移动的位数由get(i,j)
返回的编号决定。因为get(i,j)
返回的是0到15之间的整数,所以op>>get(i,j)
把op
的二进制表示向右移动对应的位数。 -
&1
:这是一个位与操作,用来提取op>>get(i,j)
的最低位。因为要判断第(i,j)
个灯泡是否被按下,所以只需要看op>>get(i,j)
的最低位是0还是1,即看这个位置上的灯泡是否被按下。
综合起来,op>>get(i,j)&1
这个表达式可以判断在当前的操作 op
中,第 (i,j)
个灯泡是否被按下,如果结果为1,则表示被按下,如果结果为0,则表示未被按下。
5. main
函数:程序的入口。
-
首先使用循环读取4行字符串,表示初始的灯泡布局。
-
然后定义了一个空的vector res,用来存储最优解。
-
接着使用一个循环遍历所有的操作可能性,操作用一个16位的二进制数表示。其中每一位代表了一个灯泡是否按下,共有16个灯泡,所以有1<<16(即2的16次方)种可能性。
-
在每次循环中,先备份当前的灯泡布局。
-
然后根据当前操作进行灯泡的按压,并将按压操作记录到temp中。
-
判断按压后的灯泡布局是否所有灯泡都亮起来了,如果是,则更新最优解res。
-
恢复备份的灯泡布局,准备下一次循环。
运行结果: