枚举例题四:熄灯问题(POJ 1222)

描述

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

img

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

img

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。根据上面的规则,我们知道1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;2)各个按钮被按下的顺序对最终的结果没有影响;3)对第1行中每盏点亮的灯,按下第2行对应的按钮,就可以熄灭第1行的全部灯。如此重复下去,可以熄灭第1、2、3、4行的全部灯。同样,按下第1、2、3、4、5列的按钮,可以熄灭前5列的灯。

输入

  • 第一行是一个正整数N,表示需要解决的案例数。
  • 每个案例由5行构成,每一行包括6个数字(0或1)。
  • 相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出

  • 对每个案例,首先输出一行,输出字符串“PUZZLE #m”,其中m是该案例的序号。
  • 接着按照该案例的输入格式输出5行:每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

样例输入

2
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
0 0 1 0 1 0
1 0 1 0 1 1
0 0 1 0 1 1
1 0 1 1 0 0
0 1 0 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
PUZZLE #2
1 0 0 1 1 1
1 1 0 0 0 0
0 0 0 1 0 0
1 1 0 1 0 1
1 0 1 1 0 1

解题思路

只用枚举第1行或者第1列的按钮即可,下面以枚举第1行按钮的状态为例。由于第1行的灯的状态由第1行和第2行的按钮决定,因此第1行的按钮状态确定之后,为使第1行的灯全部熄灭,第2行的按钮的状态唯一确定。例如,当第1行的按钮的状态确定之后:

  • 如果 ( 1 , j ) (1,j) (1,j)上的灯是点亮的,则须按下 ( 2 , j ) (2,j) (2,j)处的按钮;
  • 如果 ( 1 , j ) (1,j) (1,j)上的灯是熄灭的,则不能按 ( 2 , j ) (2,j) (2,j)处的按钮。

由此,程序只需要在枚举第1行按钮的状态之后,再一次确定各行按钮的状态,并判断最后一行的灯是否全部熄灭即可。需要枚举的状态数为 2 6 = 64 2^6=64 26=64
枚举例题四:熄灯问题(POJ 1222)_第1张图片

完整代码

#include 
#include 
#include 
#include 
using namespace std;

int GetBit(char c, int i)
{
    //取c的第i位
    return (c >> i) & 1;
}

void SetBit(char &c, int i, int v)
{
    //设置c的第i位为v
    if (v)
        c |= (1 << i);
    else
        c &= ~(1 << i);
}

void Flip(char &c, int i)
{
    //将c的第i位取反
    c ^= (1 << i);
}

void OutputResult(int n, char result[])
{
    //输出结果
    cout << "PUZZLE #" << n << endl;
    for (int i = 0; i < 5; ++i)
    {
        for (int j = 0; j < 6; ++j)
        {
            cout << GetBit(result[i], j);
            if (j < 5)
                cout << " ";
        }
        cout << endl;
    }
}

int main()
{
    char oriLights[5]; //最初灯矩阵,一个比特表示一盏灯
    char lights[5];    //不停变化的灯矩阵
    char result[5];    //结果开关矩阵
    char switchs;      //某一行的开关状态
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        memset(oriLights, 0, sizeof(oriLights));
        for (int i = 0; i < 5; ++i)
        {
            for (int j = 0; j < 6; ++j)
            {
                //读入最初灯状态
                int s;
                cin >> s;
                SetBit(oriLights[i], j, s);
            }
        }
        for (int n = 0; n < 64; ++n)
        {
            //遍历首行开关的64种状态
            memcpy(lights, oriLights, sizeof(oriLights));
            switchs = n; //第i行的开关状态
            for (int i = 0; i < 5; ++i)
            {
                result[i] = switchs; //第i行的开关方案
                for (int j = 0; j < 6; ++j)
                {
                    if (GetBit(switchs, j))
                    {
                        if (j > 0)
                            Flip(lights[i], j - 1); //改左灯
                        Flip(lights[i], j);         //改开关位置的灯
                        if (j < 5)
                            Flip(lights[i], j + 1); //改右灯
                    }
                }
                if (i < 4)
                    lights[i + 1] ^= switchs; //改下一行的灯
                switchs = lights[i];          //第i+1行开关方案和第i行灯情况相同
            }
            if (lights[4] == 0)
            {
                OutputResult(t, result);
                break;
            }
        }
    }
    return 0;
}

你可能感兴趣的:(程序设计实习,算法,c语言)