算法——熄灯问题

//解决熄灯问题(将所有灯都熄灭)
//输入:
/*
第一行输入一个正整数N,表示需要解决的案例数
每个案例为5*6的矩阵(灯的初始状态)
这些数字以空格隔开,0熄灭,1点亮
*/
//输出:
/*
对每个案例,首先输出一行
输出字符串“PUZZLE #m”,其中m是该案例的序号
接着按照该案例的输入格式输出5行
1表示需要把对应的按钮按下
0表示不需要按
每个数字以空格隔开
*/
//分析:
/*
每个按钮最多只需要按下一次
各个按钮按下的顺序没有影响
第一想法:枚举所有可能的按钮开关状态,
对每个状态计算一下最后灯的情况,看是否都熄灭(2的30次方,不可取)
基本思路:如果存在某个局部,一旦这个局部状态被确定,
那么剩余其他部分的状态,只能是确定的一种,或者不多的n种,
那么只需要枚举这个局部状态即可(第1行就可以当做一个局部)
为熄灭第1行的灯,第2行的开关状态是唯一的,
同理,为熄灭第2行的灯,第3行的开关状态也唯一
如果只枚举第1行:2的6次方种状态=64种状态
同理如果只枚举第1列:2的5次方种状态=32种状态
枚举方法:
1.六重for循环
2.将每一行看做二进制数
3.给定press第1行的取值,计算press的其他行的值
核心代码(知道第1行,计算其下方灯的按不按状态):
press[r+1][c]=(puzzle[r][c]+press[r][c-1]+press[r][c]+press[r][c+1]+press[r-1][c])%2;
上述分析:
假设我们要求(2,2)按或不按,要判断(1,2)状态及其其他周边按钮是否按下,
进而判断(2,2)需不需要按下;
eg:(1,2)的状态+(1,1)按否+(1,2)安否+(1,3)按否+(0,2)按否
按了或者亮的状态为1,否则为0;将这些结果相加后,
若为偶数,则处于熄灭状态,(2,2)不需要按下
若为奇数,则处于点亮状态,(2,2)需要按下
(1,1)到(5,6)
*/
#include<iostream>
using namespace std;

bool guess(int puzzle[6][8],int press[6][8]){
    //判断当前枚举的按扭状态,是否满足将所有灯都熄灭的状态
    //传入的press数组只枚举了第1行所以接下来要计算其他行的情况
    //再进行判断能否将灯全部熄灭
    /*
    用6*8按钮矩阵来简化下一行按钮值的计算公式
    根据已知的状态puzzle数组和按钮press[1][]
    用公式(press[r+1][c]=(puzzle[r][c]+press[r][c-1]+press[r][c]+press[r][c+1]+press[r-1][c])%2;)
    计算使得1-4行所有灯熄灭的press其他行的值,
    再判断所计算的press数组能否熄灭矩阵第5行的所有灯(若第5行所有灯都能被熄灭,那即可全部熄灭)
    */
    int r,c;
    //根据给定的press按钮按下情况第1行和puzzle数组,计算press其他行的值
    for(r=1;r<5;r++)
    {
        for(c=1;c<7;c++)
        {
            press[r+1][c]=(puzzle[r][c]+press[r][c-1]+press[r][c]+press[r][c+1]+press[r-1][c])%2;
        }
    }
    //判断计算得到的press数组能否熄灭第5行的所有灯
    for(c=1;c<7;c++)
    {
        if((press[5][c-1]+press[5][c]+press[5][c+1]+press[4][c])%2!=puzzle[5][c])
            return false;
    }
    return true;
}
void enumerate(int puzzle[6][8],int press[6][8]){
    //用来枚举press第1行元素的状态,用二进制数累加
    /*
    press[1][]中每一个元素表示一个二进制数位0/1,
    左边低位,右边高位
    通过模拟二进制加法方式实现枚举(需要进位)
    */
    int c;
    bool success;
    //初始化
    for(c=1;c<7;c++)
    {
        press[1][c]=0;
    }
    while(guess(puzzle,press)==false){
        //枚举第一行
        press[1][1]++;
        c=1;
        //模拟二进制数进位操作(左边低位,右边高位00-->10-->11-->001)
        while(press[1][c]>1){
            press[1][c]=0;
            c++;
            press[1][c]++;
        }
    }
    return;
}
int main()
{
    int cases,i,r,c;
    cout<<"请输入案例数:";
    cin>>cases;
    int puzzle[6][8],press[6][8];
    //初始化多余的左右两列
    for(r=0;r<6;r++)
        press[r][0]=press[r][7]=0;
    //初始化多余的最上面一行
    for(c=1;c<7;c++)
        press[0][c]=0;
    for(i=0;i<cases;i++){
        //第i+1个案例
        cout<<"请输入第"<<i+1<<"个案例:"<<endl;
        //输入值,初始化灯的状态
        for(r=1;r<6;r++){
            for(c=1;c<7;c++){
                cin>>puzzle[r][c];
            }
        }
        enumerate(puzzle,press);
        cout<<"案例"<<i+1<<"结果为:"<<endl;
        for(r=1;r<6;r++){
            for(c=1;c<7;c++){
                cout<<press[r][c];
            }
            cout<<endl;
        }

    }


    return 0;
}
点赞