1. 题目背景与核心挑战
这道来自Acwing题库的95号题目"费解的开关",是一个经典的递推与位运算结合的算法问题。题目描述一个5x5的灯泡矩阵,每次点击某个灯泡会同时改变其上下左右及自身的状态(开变关,关变开)。给定初始状态,要求在最少的操作次数内将所有灯泡点亮。
这类开关问题在算法竞赛中具有典型性,其核心在于:
- 状态压缩:用二进制数表示灯泡的开关状态
- 递推关系:当前行的操作由上一行的状态决定
- 枚举技巧:首行操作需要穷举所有可能性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路拆解
2.1 问题建模
首先将5x5的灯泡矩阵建模为5个5位二进制数。例如:
code复制00100
01010
00100
01010
00100
可以表示为[0b00100, 0b01010, 0b00100, 0b01010, 0b00100]
2.2 关键观察点
- 操作顺序无关性:操作的顺序不影响最终结果
- 操作不重复原则:同一个位置操作两次等于没有操作
- 递推决定性:第i行的操作完全由第i-1行的状态决定
2.3 算法框架
- 枚举第一行的所有可能操作(共2^5=32种情况)
- 对于每种情况,依次处理第2-5行:
- 查看上一行未点亮的灯泡
- 在当前位置下方进行操作
- 检查最后一行是否全部点亮
- 记录满足条件的最小操作次数
3. 核心代码实现
3.1 状态表示与操作
cpp复制const int N = 5;
char g[N][N], backup[N][N]; // 原始状态和备份
// 执行点击操作
void turn(int x, int y) {
int dx[5] = {0, -1, 0, 1, 0}, dy[5] = {0, 0, 1, 0, -1};
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) {
g[a][b] ^= 1; // 状态取反
}
}
}
3.2 主算法逻辑
cpp复制int solve() {
int res = 10; // 初始设为大于6的值
for (int op = 0; op < 32; op++) { // 枚举第一行所有操作
memcpy(backup, g, sizeof g);
int step = 0;
// 处理第一行
for (int i = 0; i < 5; i++)
if (op >> i & 1) {
step++;
turn(0, i);
}
// 递推处理2-4行
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
if (g[i][j] == '0') {
step++;
turn(i + 1, j);
}
// 检查最后一行
bool success = true;
for (int j = 0; j < 5; j++)
if (g[4][j] == '0') {
success = false;
break;
}
if (success) res = min(res, step);
memcpy(g, backup, sizeof backup);
}
return res > 6 ? -1 : res;
}
4. 算法优化与证明
4.1 时间复杂度分析
- 外层循环:32次枚举
- 内层处理:5x5的矩阵处理
- 总复杂度:O(32 * 25) = O(800)
4.2 正确性证明
- 完备性:枚举了第一行所有可能的操作组合
- 最优性:由于操作顺序无关,总能找到最小操作次数
- 无后效性:每行的操作只依赖于上一行状态
5. 常见问题与调试技巧
5.1 典型错误
- 边界处理不当:忘记检查数组越界
- 状态恢复遗漏:每次枚举后未恢复初始状态
- 操作次数计算错误:未正确统计step
5.2 调试建议
- 打印中间状态:
cpp复制void print() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++)
cout << g[i][j];
cout << endl;
}
cout << endl;
}
- 小规模测试用例:
code复制输入:
00000
00000
00100
00000
00000
正确输出:
1
6. 算法扩展与变种
6.1 N×N推广
对于N×N的开关问题:
- 首行枚举2^N种情况
- 时间复杂度变为O(2^N * N^2)
- 当N>15时需要考虑剪枝或启发式算法
6.2 三维开关问题
增加z轴维度,操作影响前后左右上下六个方向:
- 状态表示使用三维数组
- 操作枚举复杂度更高
- 通常需要双向BFS或A*算法
7. 竞赛应用与技巧
- 位运算优化:使用整数的位表示状态
- 预处理技巧:预先计算操作影响掩码
- 对称性剪枝:利用对称性减少枚举次数
实际比赛中遇到类似问题,建议先手算小规模案例,验证算法正确性后再编码实现。这类问题往往考察对问题本质的洞察力,而非复杂的编码技巧。
