Visitors hit counter dreamweaver

枚举--遍历搜索空间的例子:熄灯问题

问题描述
  有一个由按钮组成的矩阵,其中每行有6 个按钮,共5 行。每个按钮的位置上有一盏灯。
当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,
如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮
改变3 盏灯的状态;在矩阵边上的按钮改变4 盏灯的状态;其他的按钮改变5 盏灯的状态。
在下图8-1 中,左边矩阵中用X 标记的按钮表示被按下,右边的矩阵表示灯状态的改变。
与一盏灯毗邻的多个按钮被按下时,一次操作会抵消另一次操作的结果。在图8-2 中,第2
行第3、5 列的按钮都被按下,因此第2 行、第4 列的灯的状态就不改变。根据上面的规则,
我们知道:
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 行,其中的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

 

我的解题思路:

  首先根据题目的提示我们可以知道,每行(列)的亮灭只需它下一行(列)的相应的灯来控制。那么我们就可想想到,第一行靠第二行的控制来全灭,同理,第二、三、四行都可以全灭。但这是还有第五行怎么控制呢?没办法了。。。。就在这卡住了。肿么办?枚举!对了,就是枚举!既然最后一行我们不可以控制,那么我们就可以枚举第一行按灯的情况。按或不按,总共2^6次方种情况,不多,只要把第一行的按灯情况,确定了,那么后面的都好办了。然后就遍历第一行的每种按灯情况,看一下哪一种情况下,刚刚好第五行的灯也全灭,那么这个就是我们想要的情况了。

  但是在具体程序的实现上,还有很多的技巧要注意的。首先,它是个5X6的矩阵,但是我却用了6x7的矩阵,这样在按边上(5X6的边)的灯的时候,我们也同样适用是五个灯同时变了,这样用一个函数就能实现,不用考虑两种情况。最后输出只把5X6矩阵输出就好了。对于第一行的情况的遍历,我用的是递归的方式来遍历。因为前段时间刚刚好把递归(recursion)又学了一遍。使用递归来实现遍历,屡试不爽啊!主要注意的是状态的恢复,主要是递归退出时候的状态恢复。其它的就没有什么了。具体的看代码吧。

//遍历搜索空间的例子:熄灯问题

#include <stdio.h>
#include <stdlib.h>

int puzzl[7][8],press[7][8];  //这有个技巧
int nCase;
FILE *fp;


void input_data()
{
    int row,col;
    for (row = 1; row < 6; row++)
    {
        for (col = 1; col < 7; col++)
        {
            fscanf(fp,"%d",&puzzl[row][col]);
        }
    }
}

void print_press()
{
    int row,col;
    for (row = 1; row < 6; row++)
    {
        for (col = 1; col < 7; col++)
        {
            printf("%d ",press[row][col]);
        }
        printf("\n");
    }
}

void do_press(int row,int col)
{
    int j;
    for (j = -1; j < 2; j++)
    {
        if (puzzl[row][col+j] == 1)
        {
            puzzl[row][col+j] =0;
        }
        else
        {
            puzzl[row][col+j] = 1;
        }
    }

    for (j = -1; j < 2; j++)
    {
        if (j == 0)
        {
            continue;
        }
        if (puzzl[row+j][col] == 1)
        {
            puzzl[row+j][col] = 0;
        }
        else
        {
            puzzl[row+j][col] = 1;
        }
    }

    press[row][col] ^= 1;
    
}

void deal_first_line(int col)  //col 从1开始
{
    int i,m,n,nFlag;
    if (col == 7)
    {//此时,第一行的状态已经定了。
    
        for (m = 1; m < 5; m++)
        {
            for (n = 1; n < 7; n++)
            {
                if (puzzl[m][n] == 1)
                {
                    do_press(m+1,n);
                }
            }
        }

        nFlag = 1;
        for (i = 1; i < 7; i++)
        {
            if (puzzl[5][i] == 1)
            {
                nFlag = 0;
                break;
            }
        }

        if (nFlag)
        {//最后一行全为零
            printf("PUZZLE #%d\n",nCase);
            print_press();
            return;
        }

    }
    else
    {
        for (i = 0; i < 2; i++)   // two status
        {
            if (i == 1)
            {
                do_press(1,col);
            }

            deal_first_line(col + 1);

            if (i == 1)
            {
                do_press(1,col);  //若状态已改变,恢复状态。因为同一个键按两次就会恢复原始状态
            }
        }
    }
}

int main()
{
    int nTime;
    fp = fopen("in.txt","r");
    

    fscanf(fp,"%d",&nTime);

    nCase = 1;
    while (nCase <= nTime)
    {
        memset(press,0,sizeof(press));
        memset(puzzl,0,sizeof(puzzl));
        input_data();
        deal_first_line(1);
        nCase++;
    }    
}
2013/4/24 22:01

 标准答案中的代码很精简,但是我想不到。搜索的方法不只知道叫什么。。。

void enumate( )
{
     int c;
     bool success;

     for ( c = 1; c < 7; c++)
     press[1][c] = 0;

     while( guess() == false ) {
     press[1][1]++;
     c = 1;
     while ( press[1][c] > 1 ) {
     press[1][c] = 0;
     c++;
     press[1][c]++;
     }
}

 当然,我的判断的那部分也写得太拙劣了。其实按后的灯光情况我们根本就不必去考虑。在枚举出第一行的press状态后,我们可以这样来判断,技巧很重要啊!

bool guess()
{
    int row,col;
    for (row = 1; row <= 4; row++ )
    {
        for (col = 1; col <= 6; col)
        {
            press[row+1][col] = (puzzl[row][col] + press[row-1][col] + press[row][col] +
                press[row][col+1] + press[row][col-1]) % 2;
        }
    }
    for (col = 1; col <= 6)
    {
        if (puzzl[5][col] != (press[4][col] + press[5][col] + press[5][col-1] + press[5][press+1]) % 2 )
        {
            return false;
        }
    }
    return true;
}
2013/4/25 20:42

 

posted @ 2013-04-24 22:02  Jason Damon  阅读(666)  评论(0编辑  收藏  举报