文章作者:Tyan
博客:noahsnail.com ?|? CSDN ?|? 簡書
1. 枚舉
枚舉是基于逐個嘗試答案的一種問題求解策略。
2. 熄燈問題(POJ1222)
-
問題描述
有一個由按鈕組成的矩陣,其中每行有6個按鈕,共5行。每個按鈕的位置上有一盞燈。當按下一個按鈕后,該按鈕以及周圍位置(上邊、下邊、左邊、右邊)的燈都會改變一次。
Figure 1
如果燈原來是點亮的,就會被熄滅;如果燈原來是熄滅的,則會被點亮。在矩陣角上的按鈕改變3盞燈的狀態;在矩陣邊上的按鈕改變4盞燈的狀態;其他的按鈕改變5盞燈的狀態。
Figure 2
與一盞燈毗鄰的多個按鈕被按下時,一個操作會抵消另一次操作的結果。對矩陣中的每盞燈設置一個初始狀態。請你按按鈕,直至每一盞等都熄滅。 輸入
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
分析
假設當前燈亮,按鈕按一次,燈變滅,再按一次,燈又變亮,恢復到了初始狀態,因此,按鈕按兩次是沒意義的。結論:按鈕按偶數次沒意義,按鈕按奇數次與按一次一樣,因此,每個按鈕最多按一次。解題思路
- 枚舉所有可能的按鈕狀態,每種狀態計算一下最后的情況,看是否都熄滅。所有狀態數為$2^30$,因此這種方案不可行。
- 如果存在某個局部,一旦這個局部的狀態確定,那么剩下的其它狀態只能是確定的一種,或不多的n種,則只需要枚舉這個局部即可。以第一行為例,假設它就是那個局部,如果第一行的狀態確定了,是不是第二行的狀態就確定了呢?答案是是的,因為第一行按鈕按過之后,亮的燈只有按第二行才能將其熄滅。同理,第二行按鈕按下后,只能通過第三行按鈕來控制燈熄滅。
- 枚舉第一行的所有可能狀態,每個位置有0和1兩種狀態,共6個位置,因此第一行的所有可能狀態為$2^6=64$種,枚舉狀態可以通過遞歸實現。如果使用每個比特位代表一個燈的話,則可能的狀態為數字0-63。
- 方法一
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import numpy as np
# 枚舉第一行的所有可能狀態
def all_status(status_list, status, depth):
rows, columns = status.shape
other = status.copy()
other[0, depth] = 1
if depth == columns - 1:
status_list.append(status.copy())
status_list.append(other.copy())
else:
all_status(status_list, status.copy(), depth + 1)
all_status(status_list, other.copy(), depth + 1)
# 如果按鈕按下,更改燈的狀態
def light_change(input_data, i, j):
rows, columns = input_data.shape
input_data[i, j] = (input_data[i, j] + 1) % 2
if (i - 1) >= 0:
input_data[i - 1, j] = (input_data[i - 1, j] + 1) % 2
if (i + 1) < rows:
input_data[i + 1, j] = (input_data[i + 1, j] + 1) % 2
if (j - 1) >= 0:
input_data[i, j - 1] = (input_data[i, j - 1] + 1) % 2
if (j + 1) < columns:
input_data[i, j + 1] = (input_data[i, j + 1] + 1) % 2
# 嘗試關閉所有燈
def light_off(input_data, output_data):
rows, columns = input_data.shape
# 根據第一行按鈕的狀態修改燈的亮滅
for i in xrange(0, columns):
if output_data[0, i] == 1:
light_change(input_data, 0, i)
# 從第二行開始,每一行的按鈕都使上一行的燈熄滅
for i in xrange(1, rows):
for j in xrange(0, columns):
if input_data[i - 1, j] == 1:
light_change(input_data, i, j)
output_data[i, j] = 1
if np.sum(input_data) == 0:
return True, output_data
else:
return False, output_data
# 輸出指定格式的結果
def print_result(output_data):
rows, columns = output_data.shape
for i in xrange(rows):
binary_string = ''
for j in xrange(columns):
binary_string += str(output_data[i, j])
print binary_string
input_list = []
input_data = np.array([[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]], dtype = 'int8')
input_list.append(input_data)
input_data = np.array([[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]], dtype = 'int8')
input_list.append(input_data)
status_list = []
status = np.zeros((5, 6), dtype = 'int8')
all_status(status_list, status, 0)
for i in xrange(len(input_list)):
input_data = input_list[i]
for j in xrange(len(status_list)):
flag, output_data = light_off(input_data.copy(), status_list[j].copy())
if flag:
print j
print 'PUZZLE #%d' % (i + 1)
print_result(output_data)
break
- 結果
PUZZLE #1
101001
110101
001011
100100
010000
PUZZLE #2
100111
110000
000100
110101
101101
- 方法二
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
# 取特定位置上的比特,索引從0開始
def get_bit(number, index):
return (number >> index) & 1
# 設定特定位置上的比特
def set_bit(number, index, value):
return number | (value << index)
# 特定位置上的比特反轉
def flip(number, index):
return number ^ (1 << index)
# 如果按鈕按下,更改燈的狀態
def light_change(input_data, i, j):
rows = 5
columns = 6
input_data[i] = flip(input_data[i], j)
if (i - 1) >= 0:
input_data[i - 1] = flip(input_data[i - 1], j)
if (i + 1) < rows:
input_data[i + 1] = flip(input_data[i + 1], j)
if (j - 1) >= 0:
input_data[i] = flip(input_data[i], j - 1)
if (j + 1) < columns:
input_data[i] = flip(input_data[i], j + 1)
# 嘗試關閉所有燈
def light_off(input_data, output_data):
rows = 5
columns = 6
# 根據第一行按鈕的狀態修改燈的亮滅
for i in xrange(0, columns):
if get_bit(output_data[0], i) == 1:
light_change(input_data, 0, i)
# 從第二行開始,每一行的按鈕都使上一行的燈熄滅
for i in xrange(1, rows):
for j in xrange(0, columns):
if get_bit(input_data[i - 1], j) == 1:
light_change(input_data, i, j)
output_data[i] = set_bit(output_data[i], j, 1)
if input_data[-1] == 0:
return True
else:
return False
# 輸出指定格式的結果
def print_result(output_data):
for i in xrange(len(output_data)):
binary_string = bin(output_data[i])[2:]
diff = 6 - len(binary_string)
for j in xrange(diff):
binary_string = '0' + binary_string
print binary_string
input_list = []
input_data = [int('011010', 2), int('100111', 2), int('001001', 2), int('100101', 2), int('011100', 2)]
input_list.append(input_data)
input_data = [int('001010', 2), int('101011', 2), int('001011', 2), int('101100', 2), int('010100', 2)]
input_list.append(input_data)
for i in xrange(len(input_list)):
input_data = input_list[i]
for j in xrange(64):
copy = [input_data[x] for x in xrange(len(input_data))]
output_data = [0 for k in xrange(5)]
output_data[0] = j
flag = light_off(copy, output_data)
if flag:
print 'PUZZLE #%d' % (i + 1)
print_result(output_data)
break
- 結果
PUZZLE #1
101001
110101
001011
100100
010000
PUZZLE #2
100111
110000
000100
110101
101101
總結:這個問題比較復雜,其中隱含的一點就是局部狀態確定后,后面的狀態都會被確定,此時需要枚舉局部狀態。方法一與方法二的求解思路是一樣,但實現方式不一樣,方法一使用Numpy來處理數據,而方法二使用比特來處理數據。
源碼地址:Numpy方法,二進制比特方法,記得給個star。