【C/C++】开关灯游戏 蓝桥杯/ACM备考

本题考点预览:【算法:模拟】

  1. 状态压缩与枚举

    利用整数的二进制表示对灯的点击状态进行压缩和枚举。
  2. 矩阵操作与模拟

    按下按钮后,矩阵中对应灯的状态发生变化,涉及邻接元素的修改。
  3. 递归思想简化操作

    每一行的灯状态由上一行的按钮点击状态决定。
  4. 边界条件处理

    特别注意矩阵边界灯的翻转,不越界。
  5. 拷贝与回溯

    使用 memcpy 保持初始状态不变,便于尝试不同方案。

题目描述

5行6列按钮组成的矩阵,每个按钮下面有一盏灯。当按下一个按钮,该按钮和相邻4个按钮(上、下、左、右)的灯状态变反(如果是开着的,则关闭;如果是关闭的,则开起)。例如,在下图(a)中,如果作了"X"标记的按钮按下后,则各灯的状态如下图(b)所示,在该图中,阴影表示灯是开着的。游戏的目的是,从给定的初始状态出发,按下某些按钮使得所有灯都关闭。编写程序实现这一目的。

【C/C++】开关灯游戏 蓝桥杯/ACM备考_第1张图片

注意,按下一个按钮可能会取消另一个按钮按下的效果。如下图所示,按下第2行第3列和第5列的按钮后,第2行第4列的按钮的灯,由关变成开,然后又由开变成关。

【C/C++】开关灯游戏 蓝桥杯/ACM备考_第2张图片

另外请注意:

(1) 按钮按下的顺序不会影响最终的效果。

(2) 如果一个按钮按下两次,则第2次按下的效果只是取消了第1次按钮按下的效果,没有意义,所有没有哪个按钮需要按下2次。

(3) 要使得第1行的灯全关闭,只需要按下第2行中对应的按钮即可,重复这一过程,可以使得前面4行的灯全部关闭。同理,通过按下第2~6列的按钮,可以使得1~5列灯全部关闭。

输入描述

输入文件第1行为正整数n,表示测试数据的个数。每个测试数据占5行,每行有6个整数,这些整数用空格隔开,取值为0或1,0表示初始时灯是关闭的,1表示初始时灯是开着的。

输出描述

对每个测试数据,首先输出一行:"PUZZLE #m",其中m为测试数据的序号。然后输出5行,每行为6个整数,用空格隔开,取值也为0或1。这里的0和1含义跟上面的含义不一样,1表示该按钮要按下,0表示不按下。

样例输入

1
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0

样例输出

PUZZLE #1
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0

    题解: 

    #include
    #include
    
    // 定义矩阵表示灯状态(0关,1开)和按钮点击状态
    int room[5][6];        // 初始灯状态
    int room_click[5][6];  // 当前灯状态(随着操作变化)
    int result[5][6];      // 保存最终点击方案
    
    // 将整数 `n` 转换为二进制点击状态,存储在 `click` 数组中
    void get_click(int n, int click[])
    {
        int i;
        for (i = 0; i < 6; i++)
        {
            click[5 - i] = n % 2;  // 从最低位开始分解
            n /= 2;
        }
    }
    
    // 翻转灯的状态
    void flip_light(int *a, int b)
    {
        if (b)                  // 如果对应按钮被点击
            *a = 1 - *a;        // 改变灯的状态(0变1,1变0)
    }
    
    int main()
    {
        int i, j, n, flag, count, count1; 
        scanf("%d", &count);    // 读取测试用例的数量
        count1 = 0;             // 初始化结果计数器
    
        while (count--)
        {
            // 输入初始灯状态
            for (i = 0; i < 5; i++)
                for (j = 0; j < 6; j++)
                    scanf("%d", &room[i][j]);
    
            // 枚举所有可能的第1行按钮点击状态(共有2^6 = 64种)
            for (n = 0; n < 64; n++)
            {
                flag = 0;                      // 是否有剩余未关闭的灯
                int click[6] = {0};            // 第1行的按钮点击状态
                get_click(n, click);           // 将整数n转为点击状态
                memcpy(room_click, room, sizeof(room));  // 复制初始灯状态以便模拟
                for (i = 0; i < 5; i++)        // 遍历矩阵每一行
                {
                    memcpy(result[i], click, sizeof(click));  // 保存当前行的点击方案
                    for (j = 0; j < 6; j++)   // 遍历矩阵每一列
                    {
                        // 模拟点击按钮对灯状态的影响
                        if (j > 0)
                            flip_light(&room_click[i][j - 1], click[j]);  // 左侧灯
                        flip_light(&room_click[i][j], click[j]);          // 当前灯
                        if (j < 5)
                            flip_light(&room_click[i][j + 1], click[j]);  // 右侧灯
                        if (i > 0)
                            flip_light(&room_click[i - 1][j], click[j]);  // 上方灯
                        if (i < 4)
                            flip_light(&room_click[i + 1][j], click[j]);  // 下方灯
                    }
                    // 将当前行的灯状态作为下一行的点击状态
                    memcpy(click, room_click[i], sizeof(room_click[i]));
                }
                // 检查最后一行是否所有灯都关闭
                for (i = 0; i < 6; i++)
                    if (room_click[4][i] == 1)
                    {
                        flag = 1;  // 存在未关闭的灯
                        break;
                    }
                
                if (flag == 0)    // 若所有灯都关闭,输出结果
                {
                    count1++;
                    printf("PUZZLE #%d\n", count1);
                    for (i = 0; i < 5; i++)
                    {
                        for (j = 0; j < 6; j++)
                            printf("%d ", result[i][j]);  // 输出点击方案
                        printf("\n");
                    }
                }
            }
        }
        return 0;
    }
    

    本题分析:

    这是一道经典的灯阵游戏问题,核心在于枚举第1行按钮的所有点击状态,通过逐行传递状态,最终检查是否能使所有灯关闭。题目的关键点在于以下几方面:

    1. 按下按钮的影响范围
      按下一个按钮会影响本身及其上下左右相邻灯的状态,因此需要对每个灯状态的变化进行精确模拟。

    2. 状态转移的递归关系

      • 第2行按钮的点击状态完全由第1行灯状态决定;
      • 同理,第n行的灯状态又决定第n+1行的按钮点击状态。
    3. 优化搜索空间

      • 按钮的点击状态可以通过二进制数枚举,有效压缩状态空间(2^6种)。
    4. 边界处理
      矩阵的边缘灯只会受到有限的相邻灯影响,因此翻转时需注意不越界。

    5. 算法效率
      本题通过穷举所有可能的点击方案(2^6次),再逐行模拟状态变化,整体复杂度较低(64×5×6)。

    题目来源:Greater New York 2002 (ZOJ1354, POJ1222)

    你可能感兴趣的:(c语言,c++,游戏)