【算法分享】二进制枚举解决经典熄灯游戏问题💡
1. 题目描述📋
你有一个5行6列的灯泡矩阵,每次按下按钮,自己和上下左右的灯泡状态会切换。目标是把所有灯都熄灭(变成0)。
题目链接http://bailian.openjudge.cn/practice/2811/
2. 核心算法思想💡
二进制枚举🔢
-
利用整数的二进制位来表示一个状态集合中的每个元素的开关状态。
-
例如,
int x的第j位是 0 或 1,就代表第j个按钮是没按还是按下。 -
枚举
[0, 2^n)就枚举了所有可能的开关组合。整数k遍历0到7,其中k的二进制位k >> j & 1表示第j个按钮是否按下。
逐行消灯贪心策略🧹
从第一行开始,根据上一行灯的状态决定当前行的按键,逐步“清理”整个灯阵。这个贪心策略简单有效。
3. 代码解析📝
//按下按钮,影响当前的矩阵
void toggle(int x, int y) {
int dx[]={0,0,0,1,-1};
int dy[]={0,1,-1,0,0};
for(int i=0;i<5;i++) {
int nx=x+dx[i];
int ny=y+dy[i];
if(nx>=0 && nx<r && ny>=0 && ny<c) {
l[nx][ny]^=1;// ✨ 状态取反:0变1,1变0
}
}
}
bool sim(int y) {
//重置灯阵
memcpy(l,a,sizeof(a));
memset(p,0,sizeof(p));
//遍历第一行6个按钮,根据 x 的二进制位决定该按钮是否按下
for(int j=0;j<c;j++) {
if ((y>>j)&1) {
p[0][j]=1;
toggle(0,j);
}
}
//处理其余行的灯
for(int i=1;i<r;i++) {
for(int j=0;j<c;j++) {
if (l[i-1][j]==1) {
p[i][j]=1;
toggle(i,j);
}
}
}
//最后一行全为0
for (int j=0;j<c;j++) {
if (l[r-1][j]==1)
return false;
}
return true;
}
4. 算法复杂度分析⏱️
时间复杂度大约是 O(2^c * r * c),由于 c(列数)很小,枚举完全可行🎯
5. Code🎉
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int r=5;
const int c=6;
int a[8][8];
int l [r][c];
int p [r][c];
//按下按钮,影响当前的矩阵
void toggle(int x, int y) {
int dx[]={0,0,0,1,-1};
int dy[]={0,1,-1,0,0};
for(int i=0;i<5;i++) {
int nx=x+dx[i];
int ny=y+dy[i];
if(nx>=0 && nx<r && ny>=0 && ny<c) {
l[nx][ny]^=1;
}
}
}
//通过第一行灯的状态来推导整个灯阵
bool sim(int y) {
//重置灯阵
memcpy(l,a,sizeof(a));
memset(p,0,sizeof(p));
//遍历第一行6个按钮,根据 x 的二进制位决定该按钮是否按下
for(int j=0;j<c;j++) {
if ((y>>j)&1) {
p[0][j]=1;
toggle(0,j);
}
}
//处理其余行的灯
for(int i=1;i<r;i++) {
for(int j=0;j<c;j++) {
if (l[i-1][j]==1) {
p[i][j]=1;
toggle(i,j);
}
}
}
//最后一行全为0
for (int j=0;j<c;j++) {
if (l[r-1][j]==1)
return false;
}
return true;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
for (int i=0;i<5;i++) {
for (int j=0;j<6;j++)
cin>>a[i][j];
}
//枚举第一行2^r种按法
for (int k=0;k<(1<<c);k++) {
if (sim(k)){
for(int i=0;i<r;i++) {
for (int j=0;j<c;j++) {
cout<<p[i][j]<<" ";
}
cout<<endl;
}
break;
}
}
return 0;
}
如果在POJ上提交的话需要把
memcpy(l,a,sizeof(a));
改为
for (int i=0;i<r;i++)
for (int j=0;j<c;j++)
l[i][j] = a[i][j];
6. 拓展思考🚀
可以做一些类似的题目,如:
洛谷P2040 打开所有的灯:https://www.luogu.com.cn/problem/P2040
LeetCode 78. 子集( 👈每个数选或不选,枚举 2^n 种可能)

浙公网安备 33010602011771号