费解的开关

费解的开关

题目描述

你玩过“拉灯”游戏吗?

25 盏灯排成一个 5×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

题解

观察给出的数据,可以思考到一些性质:

  • 任意一个灯都不可能会被按下两次。
  • 最终的结果与按下的顺序无关。
  • 每一盏灯的状态可以由周围的 4 个灯泡确定。

因为最终结果与按下的顺序无关,那么,可以选择方便处理的方式进行遍历(这里考虑用最一般的方式枚举矩阵,即从上到下,从左到右)。同时,考虑使用每个灯的下面一盏灯来确定当前灯的状态,得到一个递推关系,即当前灯的状态为关闭的,按下当前位置下一行的灯,使当前灯打开。
现在需要考虑一个问题,就是为什么使用每个灯泡的下面一个灯泡进行点亮的策略是合理的?
可以枚举第一行的所有按法,那么,我们总能找到一个按法,该按法一定是最优解的第一行的按法。于是可以得知,第一行已经被确定了,你无法再按第一行的某个灯泡了,那你想要将第一行变为全亮的方式就一定是对第二行进行操作了,而对第二又只能操作第一行中还是灭掉的灯,于是第二行找到了一种唯一的操作方式。以此类推,当第一行的按下的方式确定,后面的所有按下方式都会被确定。
于是我们可以枚举第一行的所有操作方式,然后逐行进行判断,还需要一个bakcup数组,维护初始状态,确保处理完当前操作方式后,不会影响下一个操作方式。
需要注意的是在每次操作的过程中,最后一行无法被他的下一行确认,所以当枚举完之后需要判断最后一行是否是全亮的状态。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 7, INF = 0x3f3f3f3f;
char g[N][N], bk[N][N];
int n;
int dx[] = {0, 0, -1, 1, 0}, dy[] = {-1, 1, 0, 0, 0};
void turn(int x, int y) {
    for (int i = 0; i < 5; i ++ ) {
        int u = x + dx[i], v = y + dy[i];
        if (u >= 1 && u <= 5 && v >= 1 && v <= 5) {
            g[u][v] ^= 1;
        }
    }
}
int main()
{   
    cin >> n;
    while (n -- ) {
        for (int i = 1; i <= 5; i ++ ) cin >> (g[i] + 1);
        int res = INF;
        for (int i = 0; i < 1 << 5; i ++ ) {
            memcpy(bk, g, sizeof g);
            int s = 0;
            for (int j = 1; j <= 5; j ++ ) {
                if (i >> (j - 1) & 1) {
                    s ++;
                    turn(1, j);
                }
            }
            
            for (int j = 2; j <= 5; j ++ ) {
                for (int k = 1; k <= 5; k ++ ) {
                    if (g[j - 1][k] == '0') {
                        s ++;
                        turn(j, k);
                    }
                }
            }
            
            bool valid = true;
            for (int j = 1; j <= 5; j ++ ) {
                if (g[5][j] == '0') {
                    valid = false;
                    break;
                }
            }
            
            if (valid) res = min(res, s);
            memcpy(g, bk, sizeof g);
        } 
        
        if (res > 6) res = -1;
        cout << res << endl;
    }
    return 0;
}
posted @ 2025-03-15 22:58  uvwijk  阅读(23)  评论(0)    收藏  举报