AcWing 95.费解的开关(详解)

[题目概述]

你玩过“拉灯”游戏吗?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

输入样例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111
输出样例:
3
2
-1
  • 分析题目
    由题目可以知道这是一个连锁反应,一个灯变化,会改变上下左右四个灯,那么我们可以先模拟一下
    初始状态(画的可能有点丑,凑活看一下):
    AcWing 95.费解的开关(详解)_第1张图片
    • 先对第一行进行操作,假设就对第二个格子和第四个格子按一下
      AcWing 95.费解的开关(详解)_第2张图片
    • 现在我们第一行已经操作完了,就该看第二行该怎样操作,我们可以发现,如果想要一行全亮,那么我们第二行第一个就得按一下,并且第二行仅能进行这一个操作
      AcWing 95.费解的开关(详解)_第3张图片
    • 现在该对第三行进行操作,那么我么你就该看第二行的情况,看起来只能对第二个和第五个格子按一下
      AcWing 95.费解的开关(详解)_第4张图片
    • 在操作完三行后,我们基本上就能找到规律了。在第一行的操作确定后,后面所有的操作都是确定且唯一的,那么这个看似很复杂的问题就被简化了,只要我们枚举第一行的操作就可以。
    • 部分代码解析
      1.那么我们如何简单高效的去枚举第一行的数据?
      开关的状态就是0和1两种情况,可以将第一行开关的五个状态看成一个五位的二进制数,那将他转化为十进制数的话,相当于我们只需要32个数就可以将第一行所有的操作情况枚举出来,位置上的数是1的话就表示按一下,0就不按。
      2.新问题又来了,我们怎样知道每一位上的数是什么?
      这里有一个小操作就是将这个数右移k位,再与1进行与操作得到的就是从右往左数第k位上的数
      for (int i = 0; i < 5; i ++) {
      	if (op >> i & 1) {
      		step ++;
      		turn(0, i);// 在第一行的第i个位置按一下
      	}
      }
      
      3.在枚举第一行的操作时,我们能仅仅开一个数组吗?
      我们需要设置一个拷贝数组,将数据先备份,不然的话进行下一次枚举的时候就找不到原来的数据了
  • 全部代码(注释版)
#include 
#include 
#include 
#include 

using namespace std;
const int N = 6;
char g[N][N], backup[N][N]; // 原数组和拷贝数组,操作在g数组上




int dx[] = {0, 0, -1, 1, 0}, dy[] = {-1, 1, 0, 0, 0};// 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 > 4 || b < 0 || b > 4)
			continue;
			
		if (g[a][b] == '0')
			g[a][b] = '1';
		else
			g[a][b] = '0';
	}
}

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 ++) {
			int step = 0;
			// 将g数组的内容拷贝到backup数组中
			memcpy(backup, g, sizeof g);

			for (int i = 0; i < 5; i ++) {
				if (op >> i & 1) {
					step ++;
					turn(0, i);
				}
			}
			
			// 查看第一行到第四行灯的亮暗情况,如果是暗的话就将下一行的此位置按一下
			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 flag = true;
			for (int i = 0; i < 5; i++) {
				if (g[4][i] == '0') {
					flag = false;
					break;
				}
			}

			memcpy(g, backup, sizeof g);
			
			// 把合法方案取最小值
			if (flag) {
				res = min(res, step);
			}

		}
		if (res > 6)
			res = -1;
		cout << res << endl;
	}
	return 0;
}
  • 本题讲解完毕,还有问题的话可以敲在评论区
    有帮助的话记得点赞收藏!

你可能感兴趣的:(算法)