递归与递推-费解的开关

题目

25盏灯,5×5的方形,每一个灯都有一个开关,按下则这个灯上下左右相邻的灯的状态都发生改变。每一步都可以改变一个灯的状态。用数字1表示灯开着,0表示灯关着。编写程序判断能否在6步内使所有的灯都变亮。若6步只内无法使所有的灯都变亮,则输出-1.

分析

  • 枚举第一行:为使最终所有的灯都亮,可以考虑从第一行开始进行逐行的判断直到最后一行。首先是从第一行开始,对第一行采用枚举的方法,(每个开关有两个选择,一共有五个开关,则有2^5=32种情况)确定要操作哪个开关,目的是使第一行操作的次数最少同时亮着的灯最多。
  • 中间行递推:第一行枚举结束后,接下来只能对第二行的开关进行操作,当前情况下,第一行仍有暗着的灯,所以目前就只能通过操作第二行的对应的开关来实现使第一行暗着的灯变亮,其他行以此类推,第n(<0n<5)行开关是否需要操作,取决于第(n-1)行暗着的灯。
  • 最后一行判断:直至最后一行,判断一下最后一行既第5行操作完之后是否还有亮着的灯,如果有的话则也无法再进行操作了,就说明无法使所有的灯都变亮。

Note

  1. 常见的头文件:
  • cstdio:是C标准函数库中的头文件(standard buffered缓冲 input&output )提供基本的文字输入输出流操作。
  • cstring:封装字符串数据结构。
  • iostream:iostream是指iostream库。iostream的意思是输入输出流,直接点说就是in(输入) out(输出) stream(流),取in、out的首字母与stream合成。
  • algorithm: https://blog.youkuaiyun.com/yl_puyu/article/details/100549717
  1. memcpy 可以用来复制数组,先把原数组备份一下,然后对本数组操作,本次操作结束后,要再把备份数组还原回来,再进行下一次操作。memcpy(str1,str2,n)从str2中复制n个字节到str1中。
  2. 操作中心块周围的上下左右的格子时,可以使用坐标加偏移量的形式表示。

代码

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

using namespace std;

const int N = 6; //用字符串进行存储,字符串的最后一位为’/0‘,因此N=6而不是5
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;   //异或,不同的时候就变成相反的数
        //这里是字符串的0和1两者之间的转化,采用位运算的方法进行简化。‘0’的ASCII为48,二进制为:110000,‘1’的ASCII为49,二进制为:110001.
        //与000001进行异或则可以改变状态。
    }

}


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);
        }
        //step超过6则直接返回-1
        if(res > 6) res = -1;
        cout << res << endl;

    }
    return 0;
}

注:内容均来自https://www.acwing.com/activity/,仅个人学习笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值