本文主要是介绍AcWing95.费解的开关,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
题目
你玩过“拉灯”游戏吗?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 ≤ 500 0<n≤500 0<n≤500
输入样例
3
00111
01011
10001
11010
1110011101
11101
11110
11111
1111101111
11111
11111
11111
11111
输出样例
3
2
-1
思路
很容易发现三个性质:
- 每个位置至多只会被点击一次。
- 若固定了第 0 行(不能再改变第 0 行),则满足题意的点击方案至多只有1种。原因是:当第 i i i 行某一位为 0 时,若前 i i i 行已被固定,只能点击第 i + 1 i+1 i+1 行该位置上的数字才能使第 i i i 行的这一位变成1。从上到下按行使用归纳法可得上述结论。
- 点击的先后顺序不影响最终结果。
于是,不妨先考虑第 0 行如何点击【因为如果不改变当前第 0 行的状态,得到的结果可能并不是最优解】。在枚举第 0 行的点击方法( 2 5 = 32 2^5 = 32 25=32 种)后,就可以认为第 0 行 “固定不变”,再考虑第 1 ~ 4 行如何点击。 而按照上述性质2,此时的第 1 ~ 4 行的点击方案是确定的——从第 0 行开始递推,当第 i i i 行某一位为 0 时,点击第 i + 1 i+1 i+1 行该位置上的数字。若到达第 n − 1 n - 1 n−1 行时不全为 1 【第 n − 1 n - 1 n−1 行已经为最后一行,状态不能再修改了】,说明这种点击方式不合法。在所有合法的点击方式中取点击次数最少的就是答案。对第 0 行的 32 次枚举涵盖了该问题的整个状态空间,因此该做法是正确的。
对于第一行点击方法的枚举,可以采用位运算的方式,枚举0 ~ 31 这 32 个 5 位二进制数,若二进制数的第 k ( 0 ≤ k < 5 ) k(0 \le k \lt 5) k(0≤k<5) 位为1,就点击该矩阵第 0 行第 k k k 列的数字。
代码
#include<bits/stdc++.h>
using namespace std;const int INF = 1000000;
const int N = 5;
char matrix[N][N];//某个位置的灯状态被修改,则一共有5个位置的灯状态发生改变(上下左右中)
int dx[5] = {0, -1, 1, 0, 0}, dy[5] = {0, 0, 0, -1, 1};//修改(x,y)位置的灯的状态
void turn(int x, int y) {//(x,y) 位置上的状态发生改变,则要同步修改(a,b)位置上的状态for (int i = 0; i < 5; i++) {int a = x + dx[i];int b = y + dy[i];if (a >= 0 && a < 5 && b >= 0 && b < 5) { //判断位置是否合法matrix[a][b] ^= 1; //修改(a,b)位置的状态,如果原先是0就变为1,原先是1就变为0}}
}int work() {int ans = INF;//枚举第0行的状态(所有状态的编码为k)//第j位为1表示在第j位进行了一次操作 for (int k = 0; k < (1 << 5); k++) {int step = 0;char backup[N][N];memcpy(backup, matrix, sizeof(matrix)); //matrix复制一份放到backup中for (int j = 0; j < 5; j++) {if ((k >> j) & 1) {//如果是1,表示要第j位要按一下step++;turn(0, j);}}//第0行的状态已经固定//开始递推前四行的状态//用第i行的状态确定第i+1行的状态for (int i = 0; i < 4; i++) { //推到倒数第二行for (int j = 0; j < 5; j++) {if (matrix[i][j] == '0') { //将同列的下一个位置修改step++;turn(i + 1, j);}}}//判断最后一行是否全部为1bool successful = true;for (int j = 0; j < 5; j++) {if (matrix[4][j] == '0') {successful = false;break;}}if (successful) ans = min(ans, step);memcpy(matrix, backup, sizeof(backup)); //矩阵恢复原状}if (ans > 6) ans = -1;return ans;
}int main() {int n;cin >> n;while (n--) {for (int i = 0; i < 5; i++)cin >> matrix[i];cout << work() << endl;}return 0;
}
这篇关于AcWing95.费解的开关的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!