题目描述:http://poj.org/problem?id=1222
本题使用到的算法是枚举法。要求得使灯全部熄灭的按开关方式,最简单的方法就是将30个开关的每一种状态都枚举一遍。但是这样做需要进行2^30次计算,计算量太大。那么是否可以减少枚举的状态的数量呢?
基本思路:尝试寻找某一个“局部”,当这个“局部”的状态确定下来了以后,其余部分的情况必须根据这个“局部”来确定,从而只要唯一或不多的几种可能。如此的话,我们只需要枚举这个“局部”的每一种状态,来判断是否符合标准即可。
在本题中,我们可以将第一行,看成一个“局部”,原因如下:
1、通过题意我们知道,一个灯的状态,只与其自身和相邻的四个开关有关。那么如果在第一行的开关状态已经确定的情况下,想要改变第一行第i个灯的状态,只有按第二行第i个开关,因此,第二行的状态是唯一的。
2、同理,在第二行开关状态确定了的情况下,第三行的开关状态也是可以唯一确定的。一次类推,我们可以确定以下的每一行开关的状态。
3、确定了每一行的开关状态以后,我们还要来看看灯是否全都熄灭。由于之前开关确定的条件就是使得上一行的灯全部熄灭,因此我们只需要检查最后一行灯是否被完全熄灭。
使用这种方法,我们只需要枚举第一行的状态,总共只有2^6种,大大减少了枚举状态数目。
接下来看一下算法的具体实现:
我们用两个二维数组来表示,puzzle[][]表示灯的状态,press[][]表示开关的状态
1、在枚举第一行的状态时,我们可以用最简单地使用6次for循环。这里使用模拟二进制进位的方法来进行枚举,代码实现如下(guess()方法表示判断此情况下灯是否完全熄灭):
//用二进制加法进位,模拟第一行开关的枚举
while(guess() == false){
int c = 1;
press[1][1]++;
while(press[1][c] >1){
press[1][c] = 0;
c++;
press[1][c]++;
}
}
2、确定计算开关状态和判断最后一行灯是否熄灭的公式,根据一个灯的状态与五个开关的关系,我们可以列出关系式如下:
判断开关(r+1, c)的状态:
press[r+1][c] = (puzzle[r][c] + press[r][c] + press[r-1][c] + press[r][c+1] + press[r][c-1])%2
判断灯(5,c)熄灭的条件:
puzzle[5][c] == (press[5][c] + press[5][c-1] + press[5][c+1] + press[4][c])%2
但是我们会发现,处于边界上的开关和灯的状态,不符合这些公式,因此为了方便起见,我们将puzzle和press加上一行两列,加出来的值均为0,既:
puzzle = new int[6][8];
press = new int [6][8];
3、用enumerate()方法枚举第一行的每一种情况,用guess()方法来判断在此枚举状态下灯能否完全熄灭。
全部AC代码如下:
//poj 1222 熄灯问题
import java.util.Scanner;
public class Extended_lights_out {
public static int[][] puzzle = null;
public static int[][] press= null;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int cases = sc.nextInt();//有几个lights cases
for(int c=0; c<cases;c++){
puzzle = new int[6][8];
press = new int[6][8];//添加一行两列,使开关操作统一
//puzzle[][]和press[][]已初始化为0
for(int i = 1; i < 6 ;i++){
for(int j = 1; j < 7; j++){
puzzle[i][j] = sc.nextInt();
}
}
enumerate();
System.out.println("PUZZLE #" + (c+1));
int count = 0;
for(int r=1; r<6; r++){
for(int c1=1; c1<7; c1++){
System.out.print(press[r][c1] + " ");
}
if(count < 5){
System.out.println(" ");
count++;
}
}
}
sc.close();
}
//对第一行进行枚举
public static void enumerate(){
for(int i=1; i<7;i++){
press[1][i] = 0;
}
//用二进制加法进位,模拟第一行开关的枚举
while(guess() == false){
int c = 1;
press[1][1]++;
while(press[1][c] >1){
press[1][c] = 0;
c++;
press[1][c]++;
}
}
}
//根据第一行的枚举情况确定后面的开关状态,并判断能否实现全灭
public static boolean guess(){
for(int r = 1; r<5; r++){
for(int c = 1; c<7; c++){
press[r+1][c] = (puzzle[r][c] + press[r][c] + press[r-1][c] + press[r][c+1] + press[r][c-1])%2;
}
}
//判断第五行所有的灯是否都熄灭
for(int c=1; c<7; c++){
if(puzzle[5][c] != (press[5][c] + press[5][c-1] + press[5][c+1] + press[4][c])%2) return false;
}
return true;
}
}