蓝桥杯——1.4费解的开关

题目描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YT2wAOAl-1637457867623)(C:\Users\33385\AppData\Roaming\Typora\typora-user-images\image-20211121092321082.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQrXFhoE-1637457867626)(C:\Users\33385\AppData\Roaming\Typora\typora-user-images\image-20211121092352670.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bOyFkLQq-1637457867628)(C:\Users\33385\AppData\Roaming\Typora\typora-user-images\image-20211121092409894.png)]

笔记:

只遍历第一行,顺序可以任意,每一个状态用一个而进行串表示,每个格子只能按一次,尽量不要按两次,因为要实现最少次数。

第二行由于第一行的状态,我们是唯一确定第二行的,并且第三行的状态也是取决于第二行,每一行的开关的操作完全被前一行的灯的亮灭状态所决定

如何枚举第一行的操作,是一个指数类型枚举,可以使用位运算来进行操作,11010 = 26,可以用0 - 31来代表32中状态,i的第k位是不是1,即i >> k & 1\

一般坐标轴是这样画的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ld6woHiD-1637457910931)(C:\Users\33385\AppData\Roaming\Typora\typora-user-images\image-20211117001530142.png)]

时间复杂度,第一行是32 * 25 *5 *500

数组多开一个的是因为字符串有 '\0

字符类型的 0的ascii为48,1为49,因此判断两者其实只要比较最后一个位即可

AC代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 6;
int dx[N] = {
    
    -1, 0, 1, 0, 0}, dy[N] = {
    
    0, 1, 0, -1, 0};
char g[N][N], backup[N][N];


// 这个操作是把(x, y)以及上下左右的灯都变成相反的颜色
void turn (int x, int y)
{
    
    
    for (int i = 0; i < 5; i ++ )
    {
    
    
        int a = x + dx[i], b = y + dy[i];

        //如果在边界外边,直接忽略即可
        if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;

        g[a][b] ^= 1;   //异或,不同的时候就变成相反的数
    }

}


int main()
{
    
    
    int n;
    scanf("%d", &n);
    while(n -- )
    {
    
    
        // 按行输入,把每一行当成一个字符串
        for (int i = 0; i < 5; i ++ ) cin >> g[i];

        int res = 10;

        // 这里我们枚举了第一行的32种按法,不用管是亮是灭,把第一行所有情况都按一遍
        // 按每种情况的第一行,去遍历接下来的行
        // 枚举32种第一行的按法只是可能会减少步数,如果直接从第二行开始答案一定是固定的了,找不到最优解或者可能没有解

        for (int op = 0; op < 32; op ++ )
        {
    
    
            // 我在对这种情况操作的时候,得先备用一下
            // 把原始数组备份一下,然后操作g,操作完了还原,然后再操作
            memcpy(backup, g, sizeof g);

            int step = 0;

            // 第一行的按法(在这里 1 表示按了, 0 表示不按),这里只是为了输出第一行按完之后的状态
            for (int i = 0; i < 5; i ++ )
                if (op >> i & 1)  // 数字2 对应了 00010 表示第2个位置的按一下
                                  // 00010 >> 1 & 1  是1 所以turn(0, 1) 就是第一行第二个位置
                {
    
                     // 数字3 对应了00011 表示第1 和第2个位置的按一下
                    step ++ ;
                    turn (0, i);
                }

            // 然后通过第一行按完之后的状态,按234行
            for (int i =0; i < 4; i ++ )
                for (int j = 0; j < 5;j ++ )
                    if (g[i][j] == '0')
                    {
    
    
                        step ++;
                        turn (i + 1, j);  // 如果这个位置是灭的,就按下一行对应的位置
                    }

            bool dark = false;
            for (int j = 0; j < 5; j ++ )
                if (g[4][j] == '0')
                {
    
    
                    dark = true;
                    break;
                }


            // 对于32种情况的这一种,如果所有的全亮就记录下步数(事实上只记录了最后一行是否dark)
            if (!dark) res = min(res, step);
            memcpy (g, backup, sizeof g);
        }

        if(res > 6) res = -1;
        cout << res << endl;

    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_57095511/article/details/121449583