题目
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,减少无谓的尝试