【算法分享】二进制枚举解决经典熄灯游戏问题💡

1. 题目描述📋

你有一个5行6列的灯泡矩阵,每次按下按钮,自己和上下左右的灯泡状态会切换。目标是把所有灯都熄灭(变成0)。

题目链接http://bailian.openjudge.cn/practice/2811/

image.png

2. 核心算法思想💡

二进制枚举🔢

  1. 利用整数的二进制位来表示一个状态集合中的每个元素的开关状态。

  2. 例如,int x 的第 j 位是 0 或 1,就代表第 j 个按钮是没按还是按下。

  3. 枚举 [0, 2^n) 就枚举了所有可能的开关组合。整数 k 遍历 07,其中 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 种可能)

posted @ 2025-08-07 15:15  开珥  阅读(64)  评论(1)    收藏  举报