top1编程
← 返回题目
题解

拉灯

1 条题解

  • 0
    @ 2026-8-5 23:14:12

    P4711 拉灯(提高)

    解题思路

    这是经典的 5×5 灭灯游戏:25 盏灯排成 5 行 5 列,按一盏灯,它自己和上下左右相邻的四盏灯都会改变状态(亮的变灭、灭的变亮)。题目要求判断 6 步之内能不能把所有灯都点亮,并且输出最少需要的步数,做不到就输出 -1。

    先想清楚一个关键性质:一盏灯按两次等于没按,所以任何最优方案里,每盏灯要么不按、要么按一次。也就是说,一个"方案"就是一个由 25 个 0/1 组成的按灯矩阵。

    再想清楚第二个关键性质:只要确定了第一行哪几盏灯要按(第一行 5 盏灯,按或不按一共 2^5=32 种情况),后面每一行怎么按就被"逼"出来了:

    • 当第一行按完之后,看第二行的灯。如果第二行某一盏灯的上面(第一行那个位置)还是灭的,那这盏灭灯就只能靠按第二行这一盏来救,没有其他选择。
    • 就这样一路推下去:第二行的按法决定第三行,第三行决定第四行,第四行决定第五行。
    • 全部按完之后,检查最后一行是否全部变亮。如果全亮,说明这个第一行方案成功,记录下总共按灯的次数。

    于是算法就是:枚举第一行的 32 种按法,每种按法唯一对应一个完整方案,在所有成功的方案里取按灯次数最少的一个。如果最小值不超过 6 就输出它,否则输出 -1。

    这就像推多米诺骨牌:只要第一张牌倒的方向定了,后面每一张牌的倒下方向都是由前一张决定的,不需要再选择。

    边界情况:按灯会越界,比如最左边一盏灯的"左邻居"是不存在的,代码里要用 ni、nj 判断是否还在 5×5 范围内,越界就忽略那个方向。

    参考代码

    // 拉灯:5x5灭灯游戏,判断6步内能否全亮并求最少步数
    #include <cstdio>
    int light[6][6]; // 初始灯的状态
    int cur[6][6];   // 中间状态
    int dx[5] = {0, 0, 0, -1, 1};
    int dy[5] = {0, -1, 1, 0, 0};
    // 按第一行的按灯方案 mask 模拟,返回按灯次数,若最后一排不全亮返回很大的数
    int solveByMask(int mask) {
        for (int i = 0; i < 5; i++)
            for (int j = 0; j < 5; j++) cur[i][j] = light[i][j];
        int cnt = 0;
        // 处理第一行:按 mask 的二进制位决定是否按灯
        for (int j = 0; j < 5; j++) {
            if ((mask >> j) & 1) {
                cnt++;
                for (int k = 0; k < 5; k++) {
                    int ni = dx[k], nj = j + dy[k];
                    if (ni >= 0 && ni < 5 && nj >= 0 && nj < 5) cur[ni][nj] ^= 1;
                }
            }
        }
        // 从第二行起:上面一行若还有灭的灯,只能在当前行按
        for (int i = 1; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                if (cur[i - 1][j] == 0) {
                    cnt++;
                    for (int k = 0; k < 5; k++) {
                        int ni = i + dx[k], nj = j + dy[k];
                        if (ni >= 0 && ni < 5 && nj >= 0 && nj < 5) cur[ni][nj] ^= 1;
                    }
                }
            }
        }
        // 检查最后一行是否全亮
        for (int j = 0; j < 5; j++)
            if (cur[4][j] == 0) return 1000;
        return cnt;
    }
    int main() {
        int n;
        scanf("%d", &n);
        for (int t = 0; t < n; t++) {
            // 读入5行5个字符,跳过空格换行
            for (int i = 0; i < 5; i++) {
                for (int j = 0; j < 5; j++) {
                    char ch = getchar();
                    while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') ch = getchar();
                    light[i][j] = ch - '0';
                }
            }
            int best = 1000;
            // 枚举第一行32种按灯方案
            for (int mask = 0; mask < 32; mask++) {
                int r = solveByMask(mask);
                if (r < best) best = r;
            }
            if (best <= 6) printf("%d\n", best);
            else printf("-1\n");
        }
        return 0;
    }
    

    复杂度分析

    每组数据要枚举第一行的 32 种按法,每种按法要模拟 25 盏灯,所以单组数据大约是 32×25=800 次操作。一共有 n 组数据,总复杂度就是 O(n×32×25),几乎可以看成 O(n),速度极快。

    空间方面,只用了一个 6×6 的初始灯状态数组和一个 6×6 的临时状态数组,以及几个方向数组,全部都是常数级别的空间,与输入规模无关。

    • 1