初学算法-----枚举<熄灯问题>

了解这道问题我们要知道----位运算;

(1)如果我想知道一个二进制数sum的(从右往左数)第i位(从0开始数)为多少(以c表示): 

c=(sum>>i)&1;

(2)如果我想让一个二进制数sum的(从右往左数)第i位(从0开始数)反转:

sum^=(1<<i);

(异或的特点是:一个位上与1异或会被反转,与0异或会保持不变;)

(3)如果我想让一个二进制数sum的(从右往左数)第i位(从0开始数)根据我的想法,我输入num则其为num:

if (num==1){ sum|=(1<<i);

(或运算的特点:与0相或的数不变;与1相或一直为1)

else if (num==0){ sum &=~(1<<i);

(与运算的特点:与1相与的数不变,与0相与一直为0)

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

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

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


输入:

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出:

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

样例输入:

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

样例输出:

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<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int getBit(char c,int k)
{
    return ((c>>(5-k))&1);
}

void chanceBit(char & c,int k)//注意& c,因为这里我也想改变实参;&c是C++的能实现上述的方法; 
{
    c^=(1<<(5-k));
}

void putBit(char array[])
{
    for (int p=0;p<5;p++)
    {
        for (int q=0;q<6;q++)
        {
           printf ("%d ",getBit(array[p],q));
        }
        printf ("\n");
    }
}

char prelight[6]={},chancelight[6]={},result[6]={};
int main()
{
    int num;
    for (int i=0;i<5;i++)
    {
        for (int j=0;j<6;j++)
        {
            scanf ("%d",&num);
            prelight[i]|=(num<<(5-j));
        }
    }
    int swi;
    for (int i=0;i<64;i++)
    {
        swi=i;
        memcpy(chancelight,prelight,sizeof(prelight));
        for (int j=0;j<5;j++)
        {
            result[j]=swi;
            for (int k=0;k<6;k++)
            {
                if (getBit(result[j],k))
                {
                    if (k==0)
                    {
                        chanceBit(chancelight[j],k);
                        chanceBit(chancelight[j],k+1);
                    }
                    else if (k>0 && k<5)
                    {
                        chanceBit(chancelight[j],k);
                        chanceBit(chancelight[j],k+1);
                        chanceBit(chancelight[j],k-1);
                    }
                    else if (k==5)
                    {
                        chanceBit(chancelight[j],k);
                        chanceBit(chancelight[j],k-1);
                    }//以上是用来改变一行的;
                    if (j<4)
                    {
                        chanceBit(chancelight[j+1],k);
                    }    
                }
            }
            swi=chancelight[j];
        }
        if (chancelight[4]==0)
        {
            putBit(result);
            return 0; 
        }
    }
    return 0;
}
那么有没有不用位运算的方法?
由上述可知:一个灯是否开关(除第一行以外)取决于它上面一盏灯是否亮暗;而一盏灯的亮暗取决于1.其左边灯的开关2.其右边灯的开关3.其上面灯的开关4.其自身的开关5.原本灯的亮暗
设是否开关为press[i][j],是否亮暗为light[i][j];
有press[i][j]=(press[i-1][j]+press[i-1][j-1]+press[i-1][j+1]+press[i-2][j]+light[i-1][j])%2;(因为两次灯的开关会抵消)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int light[8][8]={},press[8][8]={},same[8]={};
void putpre()
{
    for (int i=1;i<=5;i++)
    {
        for (int j=1;j<=6;j++)
        {
            printf ("%d ",press[i][j]);
        }
        printf ("\n");
    }
}
void jin()
{
    press[1][1]++;
        for (int c=1;c<=6;c++)
        {
            if (press[1][c]>1)
            {
                press[1][c]=0;
                press[1][c+1]+=1;
            }
        }
}//不用位运算则对第一行的枚举用二进制加法进行才能从000000~111111;
bool alldown()
{
    for (int i=2;i<=5;i++)
    {
        for (int j=1;j<=6;j++)
        {
           press[i][j]=(press[i-1][j]+press[i-1][j-1]+press[i-1][j+1]+press[i-2][j]+light[i-1][j])%2;//这里不用管j-1与j+1是因为我数组开的够大,j==0与j==7时,press[i][j]==0;
        }
    }
    for (int j=1;j<=6;j++)
    {
        light[5][j]=(press[4][j]+press[5][j-1]+press[5][j+1]+press[5][j]+light[5][j])%2;
    }
    for (int j=1;j<=6;j++)
    {
        if (light[5][j]!=0)
        {
            return false;
        }
    }
    return true;
}
int main()
{
    for (int i=1;i<=5;i++)
    {
        for (int j=1;j<=6;j++)
        {
            scanf ("%d",&light[i][j]);
        }
    }
    for (int j=1;j<=6;j++)
    {
        same[j]=light[5][j];//十分注意这一步:因为我的代码会改变light[5][j],所以要让其复原;
//

// 逐一判断第五行的灯是否都熄灭
if( puzzle[5][j] != (press[5][j] + press[5][j-1] + press[5][j+1] + press[4][j]) %2 )
return false;
————————————————
版权声明:本文为CSDN博主「qianli_jiang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qianli_jiang/article/details/53420362

像这个大佬的代码完全不用担心;

    }
    while (!alldown())
    {
        for (int j=1;j<=6;j++)
        {
            light[5][j]=same[j];
        }
        jin();
    }
    putpre();
    return 0;
}
posted @ 2021-11-20 00:02  次林梦叶  阅读(63)  评论(0)    收藏  举报