题目速览:
描述:
输入输出要求:
输入:
5行组成,每一行包括6个数字(0或1)。
相邻两个数字之间用单个空格隔开。
0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。
输出:
5行组成,每一行包括6个数字(0或1)。
相邻两个数字之间用单个空格隔开。
其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。
样例:
输入:
2
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
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
输出:
PUZZLE #1 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 PUZZLE #2 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 1 0 1 0 1 1 0 1 1 0 1 注意:PUZZLE行结尾没有空格,数字行最后有一个空格。
思路:
在csdn上已经有不少博客深入解释了熄灯问题的思路,这里简单带过一下:
简单的想法是枚举二维操作列表的所有情况,但这有2的30次方种情况,显然难以实现。
我们注意到只要第一行的按法确定下来,后面行的按法也随之确定,于是我们想到枚举第一行的所有按法,并检查最后一行是否也全熄灭,如果全熄灭,则说明这种按法便是所求的答案。
这样我们只要枚举2的6次方即可,这是可行的。
代码实现思路分析:
输入输出问题:
我们首先需要一个sourse二维列表来存储灯一开始的状态。
sourse=[]
for j in range(5):
tmp=list(map(int,input().split()))#读入每行
sourse.append(tmp)
最后要把result数组(存储答案)输出出去,注意每个数字后面都有空格:
print("PUZZLE #%d"%(i+1))
for k in range(5):
for j in range(6):
print(result[k][j],end=' ')
print()#为了换行
最重要的部分:
先看代码:
for k in range(64):
light=[it.copy() for it in sourse]#注意深拷贝
t=bin(k)#利用二进制枚举
line=list(int(q) for q in t[2:])#bin函数返回的二进制数以ob开头
while len(line)!=6:
line.insert(0,0)#补齐六位
for j in range(5):#每行都要按一次,所以遍历五次
for w in range(6):#以line为标准按按键
if line[w]==1:
light[j][w]=int(not light[j][w])#被按到的变换状态,强制类型转换是因为逻辑运算结果是True 或者False
if j<4:
light[j+1][w]=int(not light[j+1][w])#被按到的按键下一行的按键变换状态
if w>0:
light[j][w-1]=int(not light[j][w-1])#左边按键(如果有的话)变换状态
if w<5:
light[j][w+1]=int (not light[j][w+1])#和上面原理相同
result.append(line)#把按的信息加入结果数组
line=light[j][:]#下一行按的标准正好是这一行目前的状态,注意深拷贝
if line==[0,0,0,0,0,0]:#如果最后一行灯正好也全熄灭,则说明找到一种答案
break
else:
result.clear()#注意如果枚举不正确的话要清空答案数组再枚举
分析:
- 对第一行按键的所有情况进行枚举时,最简单的想法是嵌套for循环或者递归回溯,但在这里我们注意到64以内的二进制数正好和所有的按键情况一一对应,因此,我们利用二进制进行枚举。
- bin()函数接收一个十进制数,返回与之对应的二进制数字符串,如bin(7)=ob111,我们发现字符串前两位为ob,因此,我们让line取ob后的数字,并向前补0至满六位。
- 我们引入line函数有许多作用,首先,它记录每一行要按的按键,并记录最终答案的每行情况。其次,我们注意到每一行需要按的按键情况,正好等于上一行灯熄灭的最终情况(如果要熄灭10101的灯,必须在下一行以10101的方式按按键),所有我们可以利用它进行迭代。最后,五行按键按完后line记录的是最后一行的数据,我们可以通过检查line是否全为0来确认灯是否熄灭。
- 我们每次模拟灯熄灭时,只考虑灯自身和灯的左右和下方,上方不考虑是因为依照line按按键上方灯一定会熄灭,同时也不影响下方的灯。
注意事项:
- 注意列表的深拷贝,尤其是二维列表。对于一维列表,我们知道直接对列表赋值是浅拷贝,改变一个列表的值,被拷贝的列表的值也会随之改变。所以我们利用切片或者调用copy()函数的方式对其进行深拷贝。但当我们用简单的切片或单次调用copy()函数拷贝 二维列表时,我们实际上是对二维列表里的每个一维列表进行了赋值,这样仍然是浅拷贝,我们应当遍历二维列表,对它的每一个一维列表进行深拷贝。可参照本题中light列表的拷贝写法。
- 逻辑表达式的结果是True 或者False。和c++不一样,如果我们不对逻辑表达式进行显示的类型转换,我们的结果列表中存储的会是True和False,而不是我们需要的0和1。
- 注意每次枚举完,都要清空result列表,或者将result列表声明在循环体内,但这样会给输出带来一些麻烦。
完整代码如下:
加上多样例的循环,代码最终呈现如下形式。
n=int(input())
for i in range(n):
sourse=[]
for j in range(5):
tmp=list(map(int,input().split()))#读入每行
sourse.append(tmp)
result=[]
for k in range(64):
light=[it.copy() for it in sourse]#注意深拷贝
t=bin(k)#利用二进制枚举
line=list(int(q) for q in t[2:])#bin函数返回的二进制数以ob开头
while len(line)!=6:
line.insert(0,0)#补齐六位
for j in range(5):#每行都要按一次,所以遍历五次
for w in range(6):#以line为标准按按键
if line[w]==1:
light[j][w]=int(not light[j][w])#被按到的变换状态,强制类型转换是因为逻辑运算结果是True 或者False
if j<4:
light[j+1][w]=int(not light[j+1][w])#被按到的按键下一行的按键变换状态
if w>0:
light[j][w-1]=int(not light[j][w-1])#左边按键(如果有的话)变换状态
if w<5:
light[j][w+1]=int (not light[j][w+1])#和上面原理相同
result.append(line)#把按的信息加入结果数组
line=light[j][:]#下一行按的标准正好是这一行目前的状态,注意深拷贝
if line==[0,0,0,0,0,0]:#如果最后一行灯正好也全熄灭,则说明找到一种答案
break
else:
result.clear()#注意如果枚举不正确的话要清空答案数组再枚举
print("PUZZLE #%d"%(i+1))
for k in range(5):
for j in range(6):
print(result[k][j],end=' ')
print()#为了换行
提交oj,过啦!