116. 飞行员兄弟--枚举-位运算

目录

116. 飞行员兄弟--枚举-位运算

输入格式

输出格式

数据范围

输入样例:

输出样例:

分析:

代码:

运行结果:


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;
}

  1. char g[N][N],backup[N][N];:定义了两个二维字符数组,g用来存储当前状态的灯泡布局,backup用来备份灯泡布局。

  2. int get(int x,int y):定义了一个函数get,用来计算在4x4的矩阵中,给定行和列的位置所对应的编号。

  3. 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) 个灯泡是否被按下的让我们来分解这个表达式:

  1. get(i,j)这个函数是用来计算在4x4的矩阵中,给定行和列的位置所对应的编号。根据代码中的定义,它把 (i,j) 映射到一个0到15之间的唯一整数。

  2. op>>get(i,j)这是对整数 op 进行右移操作,移动的位数由 get(i,j) 返回的编号决定。因为 get(i,j) 返回的是0到15之间的整数,所以 op>>get(i,j)op 的二进制表示向右移动对应的位数。

  3. &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。

  • 恢复备份的灯泡布局,准备下一次循环。

运行结果:

 

  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

captain_dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值