python解决xmuoj熄灯问题

题目速览:

描述:

输入输出要求:

输入:

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()#注意如果枚举不正确的话要清空答案数组再枚举

 分析:

  1. 对第一行按键的所有情况进行枚举时,最简单的想法是嵌套for循环或者递归回溯,但在这里我们注意到64以内的二进制数正好和所有的按键情况一一对应,因此,我们利用二进制进行枚举。
  2. bin()函数接收一个十进制数,返回与之对应的二进制数字符串,如bin(7)=ob111,我们发现字符串前两位为ob,因此,我们让line取ob后的数字,并向前补0至满六位。
  3. 我们引入line函数有许多作用,首先,它记录每一行要按的按键,并记录最终答案的每行情况。其次,我们注意到每一行需要按的按键情况,正好等于上一行灯熄灭的最终情况(如果要熄灭10101的灯,必须在下一行以10101的方式按按键),所有我们可以利用它进行迭代。最后,五行按键按完后line记录的是最后一行的数据,我们可以通过检查line是否全为0来确认灯是否熄灭。
  4. 我们每次模拟灯熄灭时,只考虑灯自身和灯的左右和下方,上方不考虑是因为依照line按按键上方灯一定会熄灭,同时也不影响下方的灯。

注意事项:

  1. 注意列表的深拷贝,尤其是二维列表。对于一维列表,我们知道直接对列表赋值是浅拷贝,改变一个列表的值,被拷贝的列表的值也会随之改变。所以我们利用切片或者调用copy()函数的方式对其进行深拷贝。但当我们用简单的切片或单次调用copy()函数拷贝 二维列表时,我们实际上是对二维列表里的每个一维列表进行了赋值,这样仍然是浅拷贝,我们应当遍历二维列表,对它的每一个一维列表进行深拷贝。可参照本题中light列表的拷贝写法。
  2. 逻辑表达式的结果是True 或者False。和c++不一样,如果我们不对逻辑表达式进行显示的类型转换,我们的结果列表中存储的会是True和False,而不是我们需要的0和1。
  3. 注意每次枚举完,都要清空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,过啦!

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值