1. 题目来源
链接:95. 费解的开关
一维递推:[递推] aw3777. 砖块(思维+递推+CF1271B)
2. 题目解析
这个是 十字形 改变灯的状态的,并且只有 5*5 的棋盘大小,所以总的状态数量是 2 5 ∗ 5 2^{5*5} 25∗5 暴力枚举肯定是不可行的。
两个性质:
- 每个点只会操作一次。
- 操作顺序是任意的。
那么,当我们操作完第一行之后,第一行已经全为 1,那么第二行的 0,只能通过操作第三行的同列元素进行改变,直到操作到第 4 行,将第 4 行变为全 1,当此时第 5 行也变为全 1 的话,则到达最终状态,是一个可行方案,更新答案即可。
在此需要注意,只是更新答案,并不是最优解。因为第一行的操作是任意的,且只有确定第一行才能递推后面的所有行,且第一行中的任意一种操作都可能是最优解中需要的操作,所以需要枚举第一行的所有操作,是 2 5 2^5 25 个,然后不断更新最优解。
不过本题,既然限制了步数的层数,那么意味着迭代加深应该也是可以做的,不过并没看到这样的写法,待思考。
时间复杂度: O ( 2 5 ∗ 5 ∗ 5 ∗ 500 = 2 e 6 ) O(2^5*5*5*500=2e6) O(25∗5∗5∗500=2e6),其中 500 是 500 个测试数据
空间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
const int N = 6;
char g[N][N], backup[N][N];
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0}; // 上右下左+自己
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 >= 5 || b < 0 || b >= 5) 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 = 1e9;
for (int op = 0; op < 1 << 5; op ++ ) { // 第一行的所有操作
memcpy(backup, g, sizeof g);
int step = 0;
for (int i = 0; i < 5; i ++ ) // 操作第一行,记录个数
if (op >> i & 1) {
step ++ ;
turn(0, i); // 改变0行i列的状态
}
for (int i = 0; i < 4; i ++ ) // 第i行决定i+1行的状态,递推即可
for (int j = 0; j < 5; j ++ ) {
if (g[i][j] == '0') {
step ++ ;
turn(i + 1, j); // 改变i+1行j列的状态
}
}
bool dark = false; // 判断最后一行是否仍有开关是灭的,为非法状态
for (int i = 0; i < 5; i ++ ) {
if (g[4][i] == '0') {
dark = true;
break;
}
}
if (!dark) res = min(res, step); // 如果合法,则为一个可行方案,答案步数取min即可
memcpy(g, backup, sizeof g);
}
if (res > 6) res = -1; // 六步内无法恢复,则输出 -1
cout << res << endl;
}
return 0;
}