枚舉——熄燈問題

文章作者: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
  • 分析
    假設當前燈亮,按鈕按一次,燈變滅,再按一次,燈又變亮,恢復到了初始狀態,因此,按鈕按兩次是沒意義的。結論:按鈕按偶數次沒意義,按鈕按奇數次與按一次一樣,因此,每個按鈕最多按一次。

  • 解題思路

  1. 枚舉所有可能的按鈕狀態,每種狀態計算一下最后的情況,看是否都熄滅。所有狀態數為$2^30$,因此這種方案不可行。
  2. 如果存在某個局部,一旦這個局部的狀態確定,那么剩下的其它狀態只能是確定的一種,或不多的n種,則只需要枚舉這個局部即可。以第一行為例,假設它就是那個局部,如果第一行的狀態確定了,是不是第二行的狀態就確定了呢?答案是是的,因為第一行按鈕按過之后,亮的燈只有按第二行才能將其熄滅。同理,第二行按鈕按下后,只能通過第三行按鈕來控制燈熄滅。
  3. 枚舉第一行的所有可能狀態,每個位置有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。

參考資料

  1. 程序設計與算法(二)算法基礎
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 基于逐個嘗試答案的一種問題求解策略 完美立方: 形如 a^3 = b^3 + c^3 + d^3 的等式稱為完美立...
    皮了個卡丘喵喵噠閱讀 260評論 0 0
  • 問題描述 有一個由按鈕組成的矩陣, 其中每行有 6 個按鈕, 共5 行。每個按鈕的位置上有一盞燈。當按下一個按鈕后...
    指尖極光閱讀 818評論 0 0
  • 誠如你曾說的你只愛你所愛的 故去千年的古老土地 也許存在著一股清流 它刻著你的風流 你承了它的素容 肆意地倘佯在夢...
    只愿妹心似我心閱讀 242評論 2 3
  • 說過的,今天要為小朋友過六一兒童節,所以,小朋友一大早就興奮地主動起床了。為了能玩得輕松和開心,在姐姐的建議下,還...
    cola的春天閱讀 653評論 6 8
  • 什么樣的人最討厭呢? 無非是不斷的傳播負能量、不停的抱怨、渾身戾氣的人。 還是學生時,罵學校、罵老師。 進入社會后...
    橙黃淡淡閱讀 200評論 0 0