问题描述
试题编号: | 202009-3 | ||||||||||||||||||||||||||||
试题名称: | 点亮数字人生 | ||||||||||||||||||||||||||||
时间限制: | 1.0s | ||||||||||||||||||||||||||||
内存限制: | 256.0MB | ||||||||||||||||||||||||||||
问题描述: | 题目背景土豪大学的计算机系开了一门数字逻辑电路课,第一个实验叫做“点亮数字人生”,要用最基础的逻辑元件组装出实际可用的电路。时间已经是深夜了,尽管实验箱上密密麻麻的连线已经拆装了好几遍,小君同学却依旧没能让她的电路正常工作。你能帮助她模拟出电路的功能,成功点亮她的数字人生吗? 问题描述本题中,你需要实现一个简单的数字逻辑电路模拟器。如果你已经有了此方面的基础,可以直接跳过本节。在阅读时,也可以参照前两个样例的图示和解释,这有助于你更好地理解数字逻辑电路的工作原理。 数字逻辑电路是用来传输数字信号(也就是二进制信号)的电路。一般来说,数字逻辑电路可以分为两大类,即组合逻辑(combinational logic)电路和时序逻辑(sequential logic)电路。在本题中,我们仅关注组合逻辑电路。这种电路仅由逻辑门(logical gate)构成。一个逻辑门可以理解为一个多输入单输出的函数,输入端连接至少一个信号,而后经过一定的逻辑运算输出一个信号。常见的逻辑门包括与(AND)、或(OR)、非(NOT)、异或(XOR)等,均与编程语言中的按位运算是对应的。 将一系列的逻辑门连接起来,就能构成具有特定功能的电路。它的功能可能很简单(如一位二进制加法只需要一个异或门),也可能极其复杂(如除法)。无论复杂程度,这类电路的特点是:它不维持任何的状态,任何时刻输出只与输入有关,随输入变化。真实世界中的逻辑器件由于物理规律的限制,存在信号传播延时。为了简单起见,本题中我们模拟的组合逻辑电路不考虑延时:一旦输入变化,输出立刻跟着变化。 考虑到组合逻辑电路的这一特性,设计时不能允许组合环路(combinational loop)的存在,即某逻辑门的输入经过了一系列器件之后又被连接到了自己的输入端。真实世界中,这种做法将导致电路变得不稳定,甚至损坏元器件。因此,你也需要探测可能的环路。需要注意,环路的存在性与逻辑门的具体功能没有任何关系;只要连接关系上存在环路,电路就无法正常工作。 输入格式输入数据包括若干个独立的问题,第一行一个整数 Q,满足 1≤Q≤Qmax。接下来依次是这 Q 个问题的输入,你需要对每个问题进行处理,并且按照顺序输出对应的答案。 每一个问题的输入在逻辑上可分为两部分。第一部分定义了整个电路的结构,第二部分定义了输入和输出的要求。实际上两部分之间没有分隔,顺序读入即可。 第一部分第一行是两个空格分隔的整数 M,N,分别表示了整个电路的输入和器件的数量,满足 1≤N≤Nmax 并且 0≤M≤kmaxN。其中 kmax 和 Nmax 都是与测试点编号有关的参数。 接下来 N 行,每行描述一个器件,编号从 1 开始递增,格式如下: None 其中
所有可能的
所有的器件均只有一个输出,但这个输出信号可以被用作多个器件的输入。 第二部分第一行是一个整数 S,表示此电路需要运行 S 次。每次运行,都会给定一组输入,并检查部分器件的输出是否正确。S 满足 1≤S≤Smax,其中 Smax 是一个与测试点编号有关的参数。 接下来的 S 行为输入描述,每一行的格式如下: None 每行有 M 个可能为 接下来的 S 行为输出描述,每一行的格式如下: None 第一个整数 1≤si≤N(1≤i≤S) 表示需要输出的信号数量。后面共有 si 个在 1 到 N 之间的数字,表示在对应的输入下,组合逻辑完成计算后,需要输出结果的器件编号。 注意 输出格式对于输入中的 Q 个问题,你需要按照输入顺序输出每一个问题的答案: 如果你检测到电路中存在组合环路,则请输出一行,内容是 如果电路可以正常工作,则请输出 S 行,每一行包含 si 个用空格分隔的数字(可能为 样例输入1 Data 样例输出1 Data 样例1说明本样例只有一个问题,它定义的组合逻辑电路结构如下图所示。其功能是一位全加器,即将三个信号相加,得到一个两位二进制数。要求的器件 2 的输出是向更高位的进位信号,器件 5 的输出是本位的求和信号。
对于第一组输入 样例输入2 Data 样例输出2 Data 样例2说明本样例也只有一个问题,它定义的组合逻辑电路结构如下图所示。
这是一个带组合环路的电路,因此无法正常工作。特别地,其中最短的环路有以下三条:
评测用例规模与约定本题共有 10 个测试点,每个测试点占 10 分。
|
题解
这道题涉及的有图的回路检测,这里使用邻接表DFS深度搜索检测回路,函数式的使用,reduce方法,基本的运算都可以使用位运算符解决;
首先把输入和器件作为节点建立单向图的数据结构,方向为器件指向它的输入端,根据器件名存储指定运算方法;
回路检测DFS深度优先检测,visited记录走过的节点,loopStack为接下来要遍历的节点,check对检测过的点进行标记优化掉重复的检测;
电路的计算根据计算状态采用递归进行,这里可以看到使用函数式编程的方便,注意题目是0,1的形式,所以使用int()来标准化。
# 点亮数字人生
from functools import reduce
if __name__ == '__main__':
q = int(input())
# 器件功能方法
func_dict = {
'NOT': lambda args: not args[0],
'AND': lambda args: reduce(lambda x, y: x & y, args),
'OR': lambda args: reduce(lambda x, y: x | y, args),
'XOR': lambda args: reduce(lambda x, y: x ^ y, args),
'NAND': lambda args: not reduce(lambda x, y: x & y, args),
'NOR': lambda args: not reduce(lambda x, y: x | y, args),
}
# 输入
for iq in range(q):
hasLoop = False
m, n = map(int, input().split())
funcs = []
for i in range(n):
funcs.append(input().split())
s = int(input())
inputs = []
for i in range(s):
inputs.append(list(map(int, input().split())))
outs = []
for i in range(s):
outs.append(list(map(int, input().split())))
# 电路图,节点编号前 m 个为输入口,后 n 个为器件
node_func = list([None for i in range(m+n)]) # **节点器件功能
node_map = [[] for i in range(m+n)] # **邻接图,前趋单向,指向器件输入节点
node_id = m
for line in funcs:
func_name, in_num, *input_ports = line # 方法名,输入数量,输入节点
node_func[node_id] = func_dict[func_name] # 绑定方法
for port in input_ports:
p_id = int(port[1:]) - 1 + (0 if port[0] == 'I' else m) # 转换节点编号
node_map[node_id].append(p_id) # 构造邻接图
node_id += 1
# 回路检测
checked = [True for x in range(m)] # 已验证无回路的节点,免检
checked.extend([False for x in range(n)])
for i in range(m, m+n): # 输入节点无前趋,略过
visited = [False for x in range(m+n)] # 记录遍历节点
loopStack = [i] # 栈
loopFind = False
while loopStack: # DFS, 深度搜索寻找回路
now_id = loopStack.pop() # 第一次遍历出栈,随后入栈,第二次遍历进行状态标记
if checked[now_id]: # 该节点前的已经在前边循环判断过,不用判断
continue
if now_id in loopStack: # 节点出现回路
loopFind = True
break
if visited[now_id]:
visited[now_id] = False
checked[now_id] = True # 无回路节点
continue
else:
visited[now_id] = True
loopStack.append(now_id)
for node_id in node_map[now_id]: # 加入所有输入节点
loopStack.append(node_id)
if loopFind:
hasLoop = True
break
if hasLoop:
print("LOOP")
continue
# 电路计算
def calc(nid, node_value):
if node_value[nid] == -1: # 未赋值
for pre in node_map[nid]:
if node_value[pre] == -1:
calc(pre, node_value) # 递归计算
# print("values:", [node_value[_id] for _id in node_map[nid]])
node_value[nid] = int(node_func[nid]([node_value[_id] for _id in node_map[nid]])) # 函数式计算当前节点值
# print("calc:", nid, "<",node_map[nid],">",node_value[nid], node_value)
return node_value[nid]
for i in range(s):
node_value = inputs[i][:]
node_value.extend([-1 for i in range(n)]) # 所有节点值,未计算为-1
object_num, *objects = outs[i]
answer = []
for object_id in objects: # 输出器件编号
node_id = object_id + m - 1
answer.append(calc(node_id, node_value))
print(" ".join([str(x) for x in answer]))