问题描述
有一个由按钮组成的矩阵,其中每行有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