題目描述: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;
}
}