[PYTHON与CSP的姻缘]202009-3点亮数字人生

题目

题目背景

土豪大学的计算机系开了一门数字逻辑电路课,第一个实验叫做“点亮数字人生”,要用最基础的逻辑元件组装出实际可用的电路。时间已经是深夜了,尽管实验箱上密密麻麻的连线已经拆装了好几遍,小君同学却依旧没能让她的电路正常工作。你能帮助她模拟出电路的功能,成功点亮她的数字人生吗?

问题描述

本题中,你需要实现一个简单的数字逻辑电路模拟器。如果你已经有了此方面的基础,可以直接跳过本节。在阅读时,也可以参照前两个样例的图示和解释,这有助于你更好地理解数字逻辑电路的工作原理。

数字逻辑电路是用来传输数字信号(也就是二进制信号)的电路。一般来说,数字逻辑电路可以分为两大类,即组合逻辑(combinational logic)电路和时序逻辑(sequential logic)电路。在本题中,我们仅关注组合逻辑电路。这种电路仅由逻辑门(logical gate)构成。一个逻辑门可以理解为一个多输入单输出的函数,输入端连接至少一个信号,而后经过一定的逻辑运算输出一个信号。常见的逻辑门包括与(AND)、或(OR)、非(NOT)、异或(XOR)等,均与编程语言中的按位运算是对应的。

将一系列的逻辑门连接起来,就能构成具有特定功能的电路。它的功能可能很简单(如一位二进制加法只需要一个异或门),也可能极其复杂(如除法)。无论复杂程度,这类电路的特点是:它不维持任何的状态,任何时刻输出只与输入有关,随输入变化。真实世界中的逻辑器件由于物理规律的限制,存在信号传播延时。为了简单起见,本题中我们模拟的组合逻辑电路不考虑延时:一旦输入变化,输出立刻跟着变化。

考虑到组合逻辑电路的这一特性,设计时不能允许组合环路(combinational loop)的存在,即某逻辑门的输入经过了一系列器件之后又被连接到了自己的输入端。真实世界中,这种做法将导致电路变得不稳定,甚至损坏元器件。因此,你也需要探测可能的环路。需要注意,环路的存在性与逻辑门的具体功能没有任何关系;只要连接关系上存在环路,电路就无法正常工作。

输入

[PYTHON与CSP的姻缘]202009-3点亮数字人生_第1张图片
[PYTHON与CSP的姻缘]202009-3点亮数字人生_第2张图片

输出

输出格式

样例

输入1

1
3 5
XOR 2 I1 I2
XOR 2 O1 I3
AND 2 O1 I3
AND 2 I1 I2
OR 2 O3 O4
4
0 1 1
1 0 1
1 1 1
0 0 0
2 5 2
2 5 2
2 5 2
2 5 2

输出1

1 0
1 0
1 1
0 0

样例1说明

[PYTHON与CSP的姻缘]202009-3点亮数字人生_第3张图片

样例输入2

1
2 6
NOR 2 O4 I2
AND 2 O4 O6
XOR 2 O5 O1
NOT 1 O6
NAND 2 O2 O2
AND 2 I1 O3
2
0 0
1 0
3 2 3 4
6 1 2 3 4 5 6

样例输出2

LOOP

样例2说明

[PYTHON与CSP的姻缘]202009-3点亮数字人生_第4张图片

测评用例规模与约定

[PYTHON与CSP的姻缘]202009-3点亮数字人生_第5张图片

题解

思路

  1. 各设备类型不一样,则运算不一样,肯定有一个函数针对设备的运算
  2. 设备运算需要输入来计算输出,则该函数需要传入设备类型与输入
  3. 设备的输出还可能作为另一个设备的输入,故需要有个数组记录所有设备的输出
  4. 输入一次性输入多次实验的输入信号和输出设备,需要“指针”epoch来记录计算哪一次的输入
  5. 输入有信号输入(I)和设备输入(O),需要区分,利用正负号区分来将编码数字化方便处理
  6. 设备输入可能出现的问题是计算时还未计算到该设备的输出,无法进行输入
    方案:记录每次遍历设备计算的结果,这样能保证所有设备都能先计算一次,直到前后两次计算的结果相同,说明所有设备已经被计算到->while循环,新旧值相等
  7. 若存在环,一些设备的输入尚未算出,就会跳过计算,这样while的条件达到稳定值即新旧相等时仍存在未计算出输出的设备(以-1标记)->若各次实验的输出列表存在-1,说明此次电路存在环
  • 函数、结构梳理:
    类型标记:字典
    输入:输入信号列表、输出设备列表
    输出:输出列表
    过程中转:设备列表
    计算:设备逻辑计算函数(分离:是否满足计算条件->获取计算数据->计算)

具体解释见代码

代码

mapping = {'NOT': 0, 'AND': 1, 'OR': 2, 'XOR': 3, 'NAND': 4, 'NOR': 5}   # 运算字典

Q = int(input().strip())  # 测试次数
for q in range(Q):
    M, N = map(int, input().strip().split())  # 电路输入,器件数量

    unit_list = []     # 设备列表
    for i in range(N):
        temp = input().strip().split()
        ttemp = []
        for j in range(2, len(temp)):
            chars = temp[j]
            if chars[0] == 'I':
                ttemp.append(int(chars[1:]))  # 信号输入直接放入编号
            else:
                ttemp.append(-1*int(chars[1:])) # 设备输入放入负值
        unit_list.append((mapping[temp[0]], ttemp)) # 设备类型、输入类型

    S = int(input())  # 测试个数
    input_list = []   # 信号输入列表
    for i in range(S):
        input_list.append(list(map(int, input().strip().split())))
    out_index_list = [] # 输出设备列表
    for i in range(S): 
        out_index_list.append(list(map(int, input().strip().split()[1:])))
    out_list = [[-1 for i in range(N)] for j in range(S)]
    # 输出列表:S行,N列

    def cal_able(iindex, epoch):
        _, coefs = unit_list[iindex] # 运算代号,输入编码
        for i in coefs:  # 遍历每一个输入编码
            if i < 0 and out_list[epoch][-1*i-1] == -1:
                # 如果是另一个设备的输出 且 其无输出,则不成立,为false
                return False
        return True # 信号输入,且设备输入均存在

    def cal(iindex, nums, epoch):
        ttype, _ = unit_list[iindex] # 获取设备类型
        if ttype == 0:
            out_list[epoch][iindex] = int(not nums[0]) # 更新输出列表
        elif ttype == 1:
            temp = 1
            for num in nums:
                temp = temp & num
            out_list[epoch][iindex] = temp
        elif ttype == 2:
            temp = 0
            for num in nums:
                temp = temp | num
            out_list[epoch][iindex] = temp
        elif ttype == 3:
            temp = nums[0]
            for i in range(1, len(nums)):
                temp = temp ^ nums[i]
            out_list[epoch][iindex] = temp
        elif ttype == 4:
            temp = 1
            for num in nums:
                temp = temp & num
            out_list[epoch][iindex] = int(not temp)
        elif ttype == 5:
            temp = 0
            for num in nums:
                temp = temp | num
            out_list[epoch][iindex] = int(not temp)

    def calculate(iindex, epoch):  # 第iindex个设备,第epoch次测试
        _, coefs = unit_list[iindex] # 获取输入编号
        nums = []
        for i in coefs: # 遍历每一个编号
            if i > 0:  # 信号输入
                nums.append(input_list[epoch][i-1]) # 计算列表中放入该编号的信号
            else:      # 设备输入
                nums.append(out_list[epoch][-1*i-1]) # 计算列表中放入该设备的输入
        cal(iindex, nums, epoch) # 对第iindex个设备 其输入为nums 进行第epoch次测试

    for epoch in range(S):
        old_ans = 0
        new_ans = sum(out_list[epoch])
        while old_ans != new_ans: # 判断是否还有未计算的设备
            # 运算中有的设备由于先计算了他的输出作为另一个设备的输入而将其跳过
            # 每次运算都是对最外层输出数组的修改,故每次变化都被保留
            # 可以由此记录是否所有设备都已运算
            old_ans = new_ans
            for i in range(len(unit_list)): # 遍历所有设备进行计算
                if cal_able(i, epoch): # 信号输入 or 设备输入均存在
                    calculate(i, epoch)
            new_ans = sum(out_list[epoch]) # 记录当前总输出‘编码’

    flag = 1     # 标记是否有环
    for epoch in range(S):   # 查看每一次测试
        if -1 in out_list[epoch]: # 若有一次测试中某个设备未被计算到输出,则有环
            print('LOOP')
            flag = 0
            break
    if flag:  # 若无环
        for epoch in range(S):
            for i in out_index_list[epoch]: # 获取每次输出设备编号
                print(out_list[epoch][i-1], end=' ')
            print()

你可能感兴趣的:(算法,python,CSP,算法)