AcWing 95. 费解的开关 (yxc代码保姆级题解+注释)

目录

题目

思路 

一、分析

二、如何枚举第一行操作?

三、turn(x,y)函数

四、结果

代码


题目

你玩过“拉灯”游戏吗?

25盏灯排成一个 5×5 的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 1表示一盏开着的灯,用数字 0表示关着的灯。

下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。

输入格式

第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。

以下若干行数据分为 n组,每组数据有 5行,每行 5 个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

输出格式

一共输出 n行数据,每行有一个小于等于 6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若 6步以内无法使所有灯变亮,则输出 −1。

数据范围

0<n≤5000

输入样例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1

思路 

一、分析

  1. 开关按的顺序可以任意
  2. 每个开关最多按一次
  3. 由于第一行决定了第二行,第二行的按法是唯一确定的
  4. 以此类推,每一行开关的操作都由前一行灯的亮灭状态唯一确定
  5. 当操作到最后一行时,可以保证前n-1行全亮,再看最后一行,若最后一行不是全亮,则没有解决方案,否则有解决方案

二、如何枚举第一行操作?

  • 指数型枚举,可以写递归
  • 使用for循环来进行位运算枚举

5个数,每个数都取0/1,共有2^5=32种方案。

转换为二进制后发现,0~2^5-1(0~31)都能分别代表5个数排列组合形式,共32种。即二进制数是从00000B开始,到11111B结束

可以用for(op=0;op<32;op++)来枚举32种方案,op代表0~31,即(00000B~11111B

注意这里的op代表着每一种方案,即告诉你哪盏灯该按,哪盏灯不该按;

        op从1~31,因为是位运算,所以是1~31的二进制和1的二进制从右向左进行与的操作,比如10100B的op,意思是第2、4位(右到左01234)需要按,不是代表灯有没有亮,32种状态其实是哪些灯要按,一种op对应一种固定的按法,所以是&1==1或者^1==0。(引自ACwing答疑评论区)

        e.g. 01101B 代表着第1/3/4盏灯要按,所以如果判断出第0/2/3位数字为1的话,则说明需要按亮这三盏灯,二进制数是从右往左数,从第0位开始算的,而在使用turn函数时,i循环是从左往右开始,故turn(0,4-i)

判断第op种方案中的第k位数字是否是1?   op右移k位与1与运算 (op>>k&1)

三、turn(x,y)函数

turn(x,y),代表x行y列的坐标,该函数用于改变(x,y)坐标周围的灯的状态,需要使用dx,dy数组把周围的偏移量都记下来,分别记录本身+上下左右共五个坐标值,若超过了0或5边界,则直接忽略;然后与1进行异或^操作,取反

 如(x,y)格子:

左边为(x+1,y) ===(1,0)

右边为(x-1,y) ===(-1,0)

上 (x,y+1) ===(0,1)

下 (x,y-1) ===(0,-1)

 把字符串中的0或1取反:与1做异或操作 (x^=1)

四、结果

第一行的32种操作方案中有32种最终按灯结果,有的没法让灯全亮,有的可以,所以在每次一种方案计算完成后,如果能让灯全亮,就都取这一次和上一次的min值,直到最后得出来的res就是32种方案里最小的按灯次数

代码

#include<bits/stdc++.h>
using namespace std;
const int N=6; ///字符串最后一位是'\0',因此要多定义一位
char g[N][N]; ///存方案
char backup[N][N];///存备份方案

///数据偏移量,左、右、上、下、本身
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0};

void turn(int x, int y){
    for (int i = 0; i < 5; i ++ ){
        int a = x + dx[i], b = y + dy[i];///(a,b)代表(x,y)周围的坐标
        if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;/// 在边界外,直接忽略即可
        g[a][b] ^= 1; ///异或操作,将(a,b)取反
    }
}

int main(){
    int t;
    cin>>t;
    while(t--){
        for(int i=0;i<5;i++)
            cin>>g[i]; ///输入初始状态的二维数组
        int res=7;///结果数
        for(int op=0;op<32;op++){  ///枚举第一行的32种方案
            memcpy(backup,g,sizeof g);
            int step=0;///按灯的步骤数
            for(int i=0;i<5;i++){
                if(op>>i&1){ ///如果第i位是1的话,按亮该盏灯
                    step++;
                    turn(0,4-i); ///二进制数的位数和for循环i开始的位数相反,所以用4-i代表需要打开的灯
                }
            }
            for(int i=0;i<4;i++){  ///第i行决定了第i+1行
                for(int j=0;j<5;j++){
                    if(g[i][j]=='0'){ ///如果当前行的灯是灭的,
                            ///那必须要按亮下一行的灯,才能让它亮起来
                        step++;
                        turn(i+1,j);
                    }
                }
            }
            bool dark = false;
            for(int i=0;i<5;i++){ ///判断最后一行的每盏灯的状态
                if(g[4][i]=='0'){
                    dark=true;
                    break;
                }
            }
            if(!dark) res=min(res,step); ///如果最后一行全亮,则得出结果数
            memcpy(g,backup,sizeof backup);///恢复g数组的状态
        }
        if(res>6) res=-1;
        cout<<res<<endl;        
    }
    return 0;
}

收获:

  1. memcpy函数的用法:memcpy(des,src,sizeof * );
  2. 位运算:右移x位后与1与运算,op>>x&1;异或运算,x与1异或运算是取反,x=x^1
  3. 坐标存储:取偏移量,类似棋盘问题,把上下左右即自身的偏移量放进两个数组里存起来,然后使用for循环来调用值
  4. 分析代码时,先按主干来分析,一步一步分析出自己需要什么变量,什么函数,函数可以最后完成

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Arduino开关是通过控制引脚的电平来实现输入或输出的设备。引用中给出了一个示例代码,演示了如何使用Arduino读取一个开关的状态并控制一个LED的亮灭。在这个示例中,当开关被按下时,引脚5会读取到低电平,并且连接在引脚13的LED会点亮。当开关没有被按下时,引脚5会读取到高电平,并且LED会熄灭。 另外,引用和引用中提供了两个改进的代码示例。在这些代码中,按下开关后LED会改变状态,再次按下开关会改变LED的状态。这样可以实现按一下亮,再按一下灭的效果。在引用中还演示了如何控制多个LED的亮灭。 综上所述,通过编写代码控制引脚电平,可以实现Arduino开关的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [创客板Arduino之开关](https://blog.csdn.net/liuming690452074/article/details/125467319)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Arduino基础入门之三按键开关](https://blog.csdn.net/u013978070/article/details/119346429)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值