枚举法-熄灯问题 - Python3版本

题目

 OpenJudge - 1222:EXTENDED LIGHTS OUT
有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。

输入

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

Sample

代码实现

import copy
from typing import List

ROWS = 5
COLS = 6


def get_bit(c, i):
    """
    获取指定字符i位的比特位置
    :param c:
    :param i:
    :return:
    """
    return (c >> i) & 1


def set_bit(c, i, v):
    """
    设置字符指定i位的比特位
    :param c:
    :param i:
    :param v:
    :return:
    """
    if v:
        c |= (1 << i)
    else:
        c &= ~(1 << i)

    return c


def flip_bit(c, i):
    """
    反转字符指定比特位
    :param c:
    :param i:
    :return:
    """
    c ^= (1 << i)
    return c


def print_result(result: List):
    """
    打印结果
    :param c:
    :return:
    """
    for i in range(ROWS):
        print(" ".join([str(get_bit(result[i], j)) for j in range(COLS)]))


def main_debug(inputs):
    origin_light = [0] * ROWS
    cur_light = [0] * ROWS
    results = [0] * ROWS

    # 原始灯状态
    for i in range(ROWS):
        for j in range(COLS):
            origin_light[i] = set_bit(origin_light[i], j, inputs[i][j])

    # 第一排的操作决定第二排的状态,因此只需要考虑第一排即可,每个元素有开和关两种,共6个元素,因此是枚举2^6种操作
    for n in range(pow(2, 6)):
        # 每种操作当前灯状态还原
        cur_light = copy.deepcopy(origin_light)
        switchs = n   # 枚举操作
        for i in range(ROWS):
            results[i] = switchs
            for j in range(COLS):
                if get_bit(switchs, j):  # 摁下开关
                    cur_light[i] = flip_bit(cur_light[i], j)
                    if j > 0:  # 左边
                        cur_light[i] = flip_bit(cur_light[i], j-1)
                    if j < COLS - 1:  # 右边
                        cur_light[i] = flip_bit(cur_light[i], j+1)

            # 下一行当前状态 (上面按了开关,则下面反转,否则不变)
            if i < ROWS - 1:
                cur_light[i+1] ^= switchs

            # 下一行的开关取决于上一行的状态(把上一行开着的全关掉)
            switchs = cur_light[i]

        # 此时确保前4行都是0,如果最后一行也是0,则是解
        if cur_light[-1] == 0:
            print(f"PUZZLE #1")
            print_result(results)
            return

    raise Exception("找不到解释")


if __name__ == '__main__':
    inputs = [
        [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],
    ]
    main_debug(inputs)

实现思路:参考北京大学的算法思路

1、首先考虑暴力破解,每位有开和关两种可能,共30位,复杂度为2^30,超过时间限制;

2、其次考虑,第一行开关的状态确定之后,其余行的开关状态也就确定了(下一行必须把上一行的灯全灭,确保上一行是全灭状态,如此类推,直到最后一行,如果是全灭,那么就是正确解),也就是说,我们只需要考虑第一行开关状态即可,最终就能推导出下一行开关状态。

3、考虑存储的数据结构,由于都是0,1且存在大量”开关“(位翻转操作),因此考虑用一位数组位运算替代二维数据,减少内存占用。

4、此思路把枚举次数从2^30降低到2^6,减少无谓的尝试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值