OpenJudge NOI 2.1 1813:熄灯问题

【题目链接】

OpenJudge NOI 2.1 1813:熄灯问题

【题目考点】

1. 枚举

2. 二进制

【解题思路】

经典的点灯游戏,有兴趣的同学可以实现这个游戏。
对1个位置点灯有2种操作,对2个位置点灯有4种操作。根据乘法原理,对30个位置的点灯操作种类数为 2 30 ≈ 1 0 9 > 1 0 7 2^{30}\approx 10^9>10^7 230109>107,如果要枚举所有可能的点灯操作种类,复杂度过高,不可行。

假设第一行所有位置的点灯操作都做完了,而第一行还有一些灯亮着,为了让这些灯熄灭,就必须在第二行相应位置点灯,而且其它位置不能点灯。

比如第一行点灯操作结束后,第一行灯的情况为0 1 0 0 1 0。在这以后不能再在第1行点灯了,那么为了让这一行灯全灭,只能在第2行第2列与第5列点灯,而且其它位置不能点灯。如果点了,会破坏第一行的情况。

因此,只要第1行的点灯操作确定了,自然就确定了第2行的点灯操作。为了使第2行的灯都灭,接下来第3行的点灯操作也确定了,依此类推,直到最后一行的点灯操作都是确定了的。最后一行点灯操作结束后,最后一行的灯未必都灭。如果最后都灭了,那么这套点灯操作就是我们要求的解。

具体过程如下:

  1. 枚举第1行的点灯操作。
  2. 此时第1行亮灯的位置,就是第2行需要点灯的位置。第2行点灯后第2行亮灯的位置就是第3行需要点灯的位置,。。。,直到完成第5行的点灯操作。
  3. 看第5行的灯是否都灭了,如果都灭了,输出记录点灯操作的矩阵。

第一行是6个灯,每盏灯只有亮暗两种情况,因此可以视为6位二进制数字。
将点灯矩阵的第一行视为高精度6位二进制数字,每次循环让这高精度6位二进制数字增1,就可以枚举到下一个二进制数字。枚举所有的6位二进制数字,即000000~111111,十进制下为0~63。

枚举第1行点灯操作的种类数为 2 6 2^6 26,针对每种第1行的点灯操作,确定整个点灯矩阵需要 4 ∗ 6 4*6 46次遍历,整体复杂度很低。

类似地也可以枚举第1列的情况,而后确认各列的情况。

【题解代码】

解法1:枚举第1行

#include<bits/stdc++.h>
using namespace std;
#define N 10
int ori_mp[N][N], mp[N][N], lt[N][N];//ori_mp:初始灯的状态 mp:灯的状态 lt[i][j]为1:在(i,j)位置进行点灯操作 
int dir[5][2] = {
    
    {
    
    0, 0}, {
    
    0, 1}, {
    
    0, -1}, {
    
    -1, 0}, {
    
    1, 0}};
void light(int sx, int sy)//在(sx, sy)点灯 
{
    
    
    for(int i = 0; i < 5; ++i)
    {
    
    
        int x = sx + dir[i][0], y = sy + dir[i][1];
        if(x >= 1 && x <= 5 && y >= 1 && y <= 6)
            mp[x][y] = !mp[x][y];//以(sx,sy)为中心的十字花位置亮暗变换 
    }
}
int main()
{
    
    
    for(int i = 1; i <= 5; ++i)
        for(int j = 1; j <= 6; ++j)
            cin >> ori_mp[i][j];
    for(int k = 0; k < 64; ++k)//枚举6位二进制数字,作为第一行的情况 
    {
    
    
        memcpy(mp, ori_mp, sizeof(ori_mp));
        for(int j = 1; j <= 6; ++j)//根据第1行点灯的情况进行点灯 
        {
    
    
            if(lt[1][j] == 1)
                light(1, j); 
        }
        for(int i = 2; i <= 5; ++i)
        {
    
    
            for(int j = 1; j <= 6; ++j)
            {
    
    
                if(mp[i-1][j] == 1)//如果(i-1,j)灯亮了,在第i-1行不能操作的情况下,只能在第(i,j)位置点灯,让(i-1,j)的灯灭掉。 
                {
    
     
                    lt[i][j] = 1;
                    light(i, j); 
                } 
                else//如果(i-1,j)的灯是灭的,在(i,j)处不能点灯 
                    lt[i][j] = 0;
            }
        }
        bool isDim = true;//第5行是否都灭了 
        for(int j = 1; j <= 6; ++j)
        {
    
    
            if(mp[5][j] == 1)
                isDim = false;
        }
        if(isDim)//如果都灭了,得到了要求的点灯的结果 
        {
    
    
            for(int i = 1; i <= 5; ++i)
            {
    
    
                for(int j = 1; j <= 6; ++j)
                    cout << lt[i][j] << ' ';
                cout << endl;
            }
            return 0;
        }
        int j = 1;
        lt[1][j]++;//把lt[1]看做一个高精度二级制数字,该操作使lt[1]这个二进制数字加1 
        while(lt[1][j] == 2)
        {
    
    
            lt[1][j] = 0;
            j++;
            lt[1][j]++;
        }
    }
    return 0;
}

解法2:枚举第1列

#include<bits/stdc++.h>
using namespace std;
#define N 10
int ori_mp[N][N], mp[N][N], lt[N][N];//ori_mp:初始灯的状态 mp:灯的状态 lt[i][j]为1:在(i,j)位置进行点灯操作 
int dir[5][2] = {
    
    {
    
    0, 0}, {
    
    0, 1}, {
    
    0, -1}, {
    
    -1, 0}, {
    
    1, 0}};
void light(int sx, int sy)//在(sx, sy)点灯 
{
    
    
    for(int i = 0; i < 5; ++i)
    {
    
    
        int x = sx + dir[i][0], y = sy + dir[i][1];
        if(x >= 1 && x <= 5 && y >= 1 && y <= 6)
            mp[x][y] = !mp[x][y];//以(sx,sy)为中心的十字花位置亮暗变换 
    }
}
int main()
{
    
    
    for(int i = 1; i <= 5; ++i)
        for(int j = 1; j <= 6; ++j)
            cin >> ori_mp[i][j];
    for(int k = 0; k < 32; ++k)//枚举5位二进制数字,作为第一列的情况 
    {
    
    
        memcpy(mp, ori_mp, sizeof(ori_mp));//将mp设为初始灯的状态 
        for(int i = 1; i <= 5; ++i)//根据第1列点灯的情况进行点灯
        {
    
    
            if(lt[i][1] == 1)
                light(i, 1); 
        } 
        for(int j = 2; j <= 6; ++j)
            for(int i = 1; i <= 5; ++i)
            {
    
    
                if(mp[i][j-1] == 1)//如果(i,j-1)灯亮了,在第j-1列不能操作的情况下,只能在第(i,j)位置点灯,让(i,j-1)的灯灭掉。 
                {
    
     
                    lt[i][j] = 1;
                    light(i, j); 
                } 
                else//如果(i,j-1)的灯是灭的,在(i,j)处不能点灯 
                    lt[i][j] = 0;
            }
        bool isDim = true;//第6列是否都灭了 
        for(int i = 1; i <= 5; ++i)
        {
    
    
            if(mp[i][6] == 1)
                isDim = false;
        }
        if(isDim)//如果都灭了,得到了要求的点灯的结果 
        {
    
    
            for(int i = 1; i <= 5; ++i)
            {
    
    
                for(int j = 1; j <= 6; ++j)
                    cout << lt[i][j] << ' ';
                cout << endl;
            }
            return 0;
        }
        int i = 1;
        lt[i][1]++;//把lt第1列看做一个高精度二级制数字,该操作使lt第1列这个二进制数字加1 
        while(lt[i][1] == 2)
        {
    
    
            lt[i][1] = 0;
            i++;
            lt[i][1]++;
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lq1990717/article/details/125700219