题目描述
你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字“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
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
【思路】:对于这个题目,我是这样想的,因为题目问的是6步内能不能到达,这个是一个5*5的地图
我就在想能不能暴力通过这个题目,我就觉得先预先处理完全部的状态,这样的话,就可
以O(1)的查询了,然后我就想到了状态压缩,把对应的25个位置压缩成一个25位的二进制
数,然后就可以存答案了,这样的话,我们逆向dfs,从1111.......111111的状态来反向
dfs 6层,状压保存答案,但是如果不进行剪枝的话,肯定是会TLE的,那么有什么状态是
可以剪枝的呢?
1.这个位置已经被走过,被走过的位置再走就又还原了,是没有意义的。
2.当前这个状态已经有结果了,已经有结果就意味着这个状态以下的所有状态都被遍历过
了,所以再往下走也是没有意义的。
我遇到的最大的问题是MLE,使用int[1 << 25]来储存答案的话,直接就莫得了,(ps:也
有可能是我写得太搓了,orz),然后我就是用了char[1 << 25]来储存答案,这样的话就ok
了。这样就可以AC了。
代码解析如下(有注释)
算法1
(逆向dfs + 状态压缩) $使用时间:4781 ms$
C++ 代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char dp[(1 << 25) + 1];///储存答案
int vids[10][10];///标记是否走过该点
int n;
int maps[10][10];///地图
char m[10][10];///转换前的地图
int moves[5][2] = {{0, 0},{0, 1},{0, -1},{1, 0},{-1, 0}};///对应的位置要进行变化
void Init()///初始化函数
{
memset(vids, 0, sizeof(vids));
for(int i = 1; i <= 5; i ++)
for(int j = 1; j <= 5; j ++)
maps[i][j] = 1;///构造出全部灯都是亮的状态
for(int i = 0; i < (1 << 25) + 1; i ++)
dp[i] = 'a';///将答案的值全部赋值为‘a’,代表这个状态没有值
}
void change(int i, int j)///走到这个位置后,对应发生的变化
{
for(int ii = 0; ii < 5; ii ++)
{
int x = i + moves[ii][0];
int y = j + moves[ii][1];
maps[x][y] = !maps[x][y];
}
}
int jzchang()///将地图的状态变为二进制数
{
int sum = ((1 << 25) - 1);///25位的1
int num = 0;
for(int i = 5; i != 0; i --)
{
for(int j = 5; j != 0; j --)
{
if(maps[i][j] == 0)
{
sum = sum ^ (1 << num);
}
num ++;
}
}
return sum;
}
void dfs(int step)
{
if(step == 6)
{
return;
}
for(int i = 1; i <= 5; i ++)
{
for(int j = 1; j <= 5; j ++)
{
if(vids[i][j] == 0)///如果这个位置没有被走过
{
vids[i][j] = 1;
change(i, j);
int temp = jzchang();
if(dp[temp] == 'a')///如果当前的状态没有被走过
{
dp[temp] = step + 1 + '0';
dfs(step + 1);
}
change(i, j);
vids[i][j] = 0;
}
}
}
}
int main()
{
scanf("%d", &n);
Init();
dp[(1 << 25) - 1] = '0';///初始状态为0
dfs(0);
while(n --)
{
for(int i = 1; i <= 5; i ++)
scanf("%s", m[i]);
for(int i = 1; i <= 5; i ++)
{
for(int j = 0; j < 5; j ++)
{
maps[i][j + 1] = m[i][j] - '0';
}
}
int t = jzchang();
if(dp[t] == 'a')
printf("-1\n");
else
printf("%d\n", dp[t] - '0');
}
return 0;
}
char dp[(1 << 25) + 1];///储存答案 这个开的太大了