枚举——熄灯问题

文章作者: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. 程序设计与算法(二)算法基础
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 基于逐个尝试答案的一种问题求解策略 完美立方: 形如 a^3 = b^3 + c^3 + d^3 的等式称为完美立...
    皮了个卡丘喵喵哒阅读 253评论 0 0
  • 问题描述 有一个由按钮组成的矩阵, 其中每行有 6 个按钮, 共5 行。每个按钮的位置上有一盏灯。当按下一个按钮后...
    指尖极光阅读 749评论 0 0
  • 诚如你曾说的你只爱你所爱的 故去千年的古老土地 也许存在着一股清流 它刻着你的风流 你承了它的素容 肆意地倘佯在梦...
    只愿妹心似我心阅读 230评论 2 3
  • 说过的,今天要为小朋友过六一儿童节,所以,小朋友一大早就兴奋地主动起床了。为了能玩得轻松和开心,在姐姐的建议下,还...
    cola的春天阅读 628评论 6 8
  • 什么样的人最讨厌呢? 无非是不断的传播负能量、不停的抱怨、浑身戾气的人。 还是学生时,骂学校、骂老师。 进入社会后...
    橙黄淡淡阅读 183评论 0 0