题目:
你玩过“拉灯”游戏吗?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
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
分析:因为我不就之前刷到过类似的题目--飞行员兄弟,在那个题目种,我们是枚举每一个点的状态--开这个点或者关这个点,在这题中我也想这么写,话不多说,给大家上代码:
#include<iostream>
#include<algorithm>
using namespace std;
int map[10][10], ans = 0x3f3f;
void change(int x, int y) {
int mx[4] = { -1, 0, 1, 0 }, my[4] = { 0, -1 ,0, 1 };
map[x][y] = 1 - map[x][y];
for (int i = 0; i < 4; i++) {
int x1 = x + mx[i], y1 = y + my[i];
if (x1 >= 1 && x1 <= 5 && y1 >= 1 && y1 <= 5) {
map[x1][y1] = 1 - map[x1][y1];
}
}
}
void dfs(int x, int y, int step)
{
//走完所有点后判断step是否小于6;矩阵是否符合要求
if (x == 5 && y == 6) {
if (step <= 6) {
int flag = true;
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
if (!map[i][j])flag = false;
}
}
if (flag == true)ans = min(ans, step);
}
return;
}
if (y == 6) x++, y = 1;
//改变点的状态
change(x, y);
dfs(x, y + 1, step + 1);
change(x, y);
//不改变点的状态
dfs(x, y + 1, step);
return;
}
int main() {
int n;
cin >> n;
cin.get();
while (n--) {
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
char a;
cin >> a;
if (a == '1')map[i][j] = 1;
else map[i][j] = 0;
}
cin.get();
}
dfs(1, 1, 0);
if (ans > 6)cout << -1;
else cout << ans;
}
return 0;
}
不出所料,TLE了,分析一下时间复杂度,首先是遍历了所有点的所有情况,时间复杂度为0(),2的25次方差不多就是10的8次方级别了,再加上当遍历完所有点后检验矩阵的合格性的时间复杂度,是肯定TLE了的。如何改进呢,这里的时间复杂度主要是高在遍历所有点上,是指数型的时间复杂度,实在太高了,我们能不能少遍历些点呢?当然可以,如果只遍历第一行点的状态呢,当我们遍历完第一行点的开关与否,如果我们想要继续改变第一行点的状态,就只能通过改变第二行点的状态来改变,以此类推,这里可以看看这篇文章:费解的开关(模拟/BFS+二进制)_熙言丶的博客-CSDN博客_费解的开关
https://blog.csdn.net/qq_41021816/article/details/81810059这样只用遍历5个点的情况,也就是0(
)的时间复杂度。看代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f;
int map[10][10], ans;
//改变(x,y)这个点的状态
void change(int x, int y, int map[10][10]){
int mx[4] = {-1, 0, 1, 0}, my[4] = {0, -1 ,0, 1};
map[x][y] = 1 - map[x][y];
for(int i = 0; i < 4; i ++){
int x1 = x + mx[i], y1 = y + my[i];
if(x1 >= 1 && x1 <= 5 && y1 >= 1 && y1 <= 5){//判断边界问题
map[x1][y1] = 1 - map[x1][y1];
}
}
}
//检查第一行状态遍历完后,矩阵是否合理,这里的step表示第一行状态遍历完后的操作数,如第一行改变了2//个点的状态,那么step就等于2
int check(int step){
int sum = step;
int a[10][10];
for(int i = 1; i <=5; i ++){
for(int j = 1;j <= 5; j ++){
a[i][j] = map[i][j];
}
}
//从第1行开始往下改变状态
for(int i = 1; i <= 4; i ++){
for(int j = 1; j <= 5;j ++){
if(!a[i][j]){
sum ++;
change(i + 1, j, a);
}
}
}
//如果所有状态都确定后,还有关了的点,就设置为INF
for(int i = 1; i <= 5; i ++){
if(!a[5][i])return INF;
}
return sum;
}
void dfs(int y, int step){
//第一行点遍历完了
if(y > 5){
ans = min(ans, check(step));
return;
}
change(1, y, map);
dfs(y + 1, step + 1);
change(1, y, map);//状态回溯
dfs(y + 1, step);
}
void dis(){
for(int i = 1; i <= 5; i ++){
for(int j = 1; j <= 5; j ++){
cout << map[i][j];
}
cout << endl;
}
}
int main(){
int n;
cin >> n;
while(n -- ){
for(int i = 1; i <= 5; i ++){
for(int j = 1; j <= 5; j ++){
scanf("%1d", &map[i][j]);
}
}
ans = INF;
dfs(1, 0);
if(ans < 7)cout << ans << endl;
else cout << -1 << endl;
}
return 0;
}
总结下,也就是第一行的状态确定后,按照我们的开关规则操作,整个矩阵的step其实也就已经确定。我们平时用dfs的时候如果点数超过5*5,那就要考虑优化问题或者剪枝了。我就是无脑DFS,结果就寄了。另外本题还有很多种解法,可以去这个网站看下(acwing yyds)
如果看不懂可以私信我哈。