点亮数字人生
时间限制: 1.0 秒
空间限制: 512 MiB
题目背景
土豪大学的计算机系开了一门数字逻辑电路课,第一个实验叫做“点亮数字人生”,要用最基础的逻辑元件组装出实际可用的电路。 时间已经是深夜了,尽管实验箱上密密麻麻的连线已经拆装了好几遍,小君同学却依旧没能让她的电路正常工作。 你能帮助她模拟出电路的功能,成功点亮她的数字人生吗?
题目描述
本题中,你需要实现一个简单的数字逻辑电路模拟器。如果你已经有了此方面的基础,可以直接跳过本节。在阅读时,也可以参照前两个样例的图示和解释,这有助于你更好地理解数字逻辑电路的工作原理。
数字逻辑电路是用来传输数字信号(也就是二进制信号)的电路。一般来说,数字逻辑电路可以分为两大类,即组合逻辑(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 开始递增,格式如下:
FUNC k L_1 L_2 ... L_k
其中 FUNC 代表具体的逻辑功能,k 表示输入的数量,后面跟着该器件的 k 个输入端描述 L,格式是以下二者之一:
- Im:表示第 m 个输入信号连接到此输入端,保证 1≤m≤M;
- On:表示第 n 个器件的输出连接到此输入端,保证 1≤n≤N。
所有可能的 FUNC 和允许的输入端数量如下表所述:
FUNC | 最少输入数量 | 最多输入数量 | 功能描述 |
---|---|---|---|
NOT | 1 | 1 | 非 |
AND | 2 | kmax | 与 |
OR | 2 | kmax | 或 |
XOR | 2 | kmax | 异或 |
NAND | 2 | kmax | 与非(先全部与后取非) |
NOR | 2 | kmax | 或非(先全部或后取非) |
所有的器件均只有一个输出,但这个输出信号可以被用作多个器件的输入。
第二部分
第一行是一个整数 S,表示此电路需要运行 S 次。每次运行,都会给定一组输入,并检查部分器件的输出是否正确。S 满足 1≤S≤Smax,其中 Smax 是一个与测试点编号有关的参数。
接下来的 S 行为输入描述,每一行的格式如下:
I_1 I_2 ... I_M
每行有 M 个可能为 0 或 1 的数字,表示各个输入信号(按编号排列)的状态。
接下来的 S 行为输出描述,每一行的格式如下:
s_i O_1 O_2 ... O_s
第一个整数 1≤si≤N(1≤i≤S) 表示需要输出的信号数量。后面共有 si 个在 1 到 N 之间的数字,表示在对应的输入下,组合逻辑完成计算后,需要输出结果的器件编号。
注意 O 序列不一定是递增的,即要求输出的器件可能以任意顺序出现。
输出格式
输出到标准输出。
对于输入中的 Q 个问题,你需要按照输入顺序输出每一个问题的答案:
如果你检测到电路中存在组合环路,则请输出一行,内容是 LOOP,无需输出其他任何内容。
如果电路可以正常工作,则请输出 S 行,每一行包含 si 个用空格分隔的数字(可能为 0 或 1),依次表示“输出描述”中要求的各个器件的运算结果。
样例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解释
本样例只有一个问题,它定义的组合逻辑电路结构如下图所示。其功能是一位全加器,即将三个信号相加,得到一个两位二进制数。要求的器件 2 的输出是向更高位的进位信号,器件 5 的输出是本位的求和信号。
对于第一组输入 0 1 1,输出是 1 0;对于第二组输入 1 0 1,输出恰好依旧是 1 0(但电路内部状态不同)。
样例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解释
本样例也只有一个问题,它定义的组合逻辑电路结构如下图所示。
这是一个带组合环路的电路,因此无法正常工作。特别地,其中最短的环路有以下三条:
6 - 2 - 5 - 3 - 6
4 - 1 - 3 - 6 - 4
2 - 5 - 3 - 6 - 2
子任务
本题共有 10 个测试点,每个测试点占 10 分。
这题不难!不过我当时没做出来🤣🤣🤣这输入真的搞死个人~~
感觉这道题就和201903-4 消息传递接口一个样,思路差不多,就看你仔不仔细了😏😏😏
这道题我没提交,所以不知道会不会超时,反正正确结果是出来了。
总结一句:我还是太菜了🥀🥀🥀
那说一下我的做法把:
-
字符串处理的太麻烦了,而且还耗时,所以我做了如下定义,将字符串映射到对应的数字。
NOT -> 0 AND -> 1 OR -> 2 XOR -> 3 NAND -> 4 NOR -> 5 ------- I -> 10000 O -> -10000
-
如果当前器件的输入是其它器件的输出,则看一下它上一级的器件输出了没有,如果没有则继续看上一级。
-
怎么判断有环呢?如果当前器件访问过了,但是还没有输出成功,则存在环。
-
存在环怎么退出来呢?搞个异常。
感觉我这个代码有点问题,如果所有器件都输出了,就判断不了是否有环了。正确的做法是拓扑判环。
我的代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;
class Reader {
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer tokenizer = new StringTokenizer("");
static String next() throws IOException {
while (!tokenizer.hasMoreTokens()) {
tokenizer = new StringTokenizer(reader.readLine());
}
return tokenizer.nextToken();
}
static String nextLine() throws IOException {
return reader.readLine();
}
static int nextInt() throws IOException {
return Integer.parseInt(next());
}
static long nextLong() throws IOException {
return Long.parseLong(next());
}
static double nextDouble() throws IOException {
return Double.parseDouble(next());
}
}
class LoopException extends Exception {
static final long serialVersionUID = 66666666666666666L;
public LoopException() {
super();
}
}
class Main {
public static void main(String[] args) {
try {
int Q = Reader.nextInt();
ArrayList<Object> result = new ArrayList<Object>();// 存Q个问题的答案
for (int i = 0; i < Q; i++) {
ArrayList<int[]> list = new ArrayList<int[]>();// 存操作和器件输入端
// m:输入端数量,n:器件数量
int m = Reader.nextInt(), n = Reader.nextInt();
for (int j = 0; j < n; j++) {
int operator = -1;
switch (Reader.next()) {// 对应操作
case "NOT":
operator = 0;
break;
case "AND":
operator = 1;
break;
case "OR":
operator = 2;
break;
case "XOR":
operator = 3;
break;
case "NAND":
operator = 4;
break;
case "NOR":
operator = 5;
break;
}
int inputNum = Reader.nextInt();// 输入端数量
int[] input = new int[inputNum + 2];
input[0] = operator;
input[1] = inputNum;
String temp = "";
for (int k = 0; k < inputNum; k++) {// 依次输入输入信号
temp = Reader.next();
if (temp.charAt(0) == 'I') {
input[k + 2] = 10000 + temp.charAt(1) - 48;
} else {
input[k + 2] = -10000 - (temp.charAt(1) - 48);
}
}
list.add(input);
}
int S = Reader.nextInt();// 电路运行次数
ArrayList<int[]> inputArray = new ArrayList<int[]>(S);// 存输入端信号状态
ArrayList<int[]> outputArray = new ArrayList<int[]>(S);// 存输出端的器件
for (int j = 0; j < S; j++) {
String[] split = Reader.nextLine().split(" ");
int[] input = new int[m + 1];
for (int k = 1; k <= m; k++) {// m个输入
input[k] = Integer.parseInt(split[k - 1]);
}
inputArray.add(input);
}
for (int j = 0; j < S; j++) {// 最终输出的端口
String[] split = Reader.nextLine().split(" ");
int[] output = new int[Integer.parseInt(split[0])];
for (int k = 0; k < output.length; k++) {
output[k] = Integer.parseInt(split[k + 1]);
}
outputArray.add(output);
}
// ------------------------------------------------
try {
for (int j = 0; j < S; j++) {// 运行S次
int[] input = inputArray.get(j);// 输入信号
int[] port = outputArray.get(j);// 最终输出端
int[] output = new int[n];// n个器件的输出
boolean[] flag = new boolean[n];// 记录该器件是否已经输出
boolean[] status = new boolean[n];// 记录是否存在循环
for (int k = 0; k < n; k++) {// 遍历所有器件
output[k] = getResult(k, list, input, output, flag, status);
}
int[] finalOutput = new int[port.length];
for (int k = 0; k < port.length; k++) {
finalOutput[k] = output[port[k] - 1];// 这里对应的输出端就是结果
}
result.add(finalOutput);
}
} catch (LoopException loop) {
result.add("LOOP");// 发生循环了
}
}
// 输出结果
for (Object obj : result) {
if (obj instanceof String) {
System.out.println(obj);
} else {
int[] array = (int[]) obj;
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.print(array[array.length - 1]);
System.out.println();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static int getResult(int k, ArrayList<int[]> list, int[] input, int[] output, boolean[] flag,
boolean[] status) throws LoopException {
if (status[k] && !flag[k]) {// 如果该器件访问过了,但是还没有输出成功,则存在环
throw new LoopException();
}
if (!flag[k]) {// 如果这个器件还没输出,则先将这个器件输出
status[k] = true;// 标记访问了该器件
int[] component = list.get(k);// 获取器件
int operator = component[0];// 是哪个操作
int length = component[1];// 几个输入端
if (operator == 0) {// NOT
if (component[2] > 0) {// 第 m 个输入信号连接到此输入端(I)
output[k] = ~(input[component[2] % 10000]);
} else {// 第 n 个器件的输出连接到此输入端(O)
// 如果该器件的输入是其它器件的输出,则看一下它上一级的器件输出了没有
output[k] = ~(getResult((-component[2] % 10000) - 1, list, input, output, flag, status));
}
} else {// 其它都是多于1个输入端的操作
int temp = component[2] % 10000;// 先把第一个赋好值
if (temp > 0) {
output[k] = input[temp];
} else {
output[k] = (getResult((-temp) - 1, list, input, output, flag, status));
}
if (operator == 1) {// AND
for (int i = 1; i < length; i++) {
if (component[i + 2] > 0) {
output[k] &= (input[component[i + 2] % 10000]);
} else {
output[k] &= (getResult((-component[i + 2] % 10000) - 1, list, input, output, flag, status));
}
}
} else if (operator == 2) {// OR
for (int i = 1; i < length; i++) {
if (component[i + 2] > 0) {
output[k] |= (input[component[i + 2] % 10000]);
} else {
output[k] |= (getResult((-component[i + 2] % 10000) - 1, list, input, output, flag, status));
}
}
} else if (operator == 3) {// XOR
for (int i = 1; i < length; i++) {
if (component[i + 2] > 0) {
output[k] ^= (input[component[i + 2] % 10000]);
} else {
output[k] ^= (getResult((-component[i + 2] % 10000) - 1, list, input, output, flag, status));
}
}
} else if (operator == 4) {// NAND
for (int i = 1; i < length; i++) {
if (component[i + 2] > 0) {
output[k] &= (input[component[i + 2] % 10000]);
} else {
output[k] &= (getResult((-component[i + 2] % 10000) - 1, list, input, output, flag, status));
}
}
output[k] = ~output[k];
} else if (operator == 5) {// NOR
for (int i = 1; i < length; i++) {
if (component[i + 2] > 0) {
output[k] |= (input[component[i + 2] % 10000]);
} else {
output[k] |= (getResult((-component[i + 2] % 10000) - 1, list, input, output, flag, status));
}
}
output[k] = ~output[k];
}
}
flag[k] = true;// 记录这个器件已经输出完成
}
return output[k];
}
}
测试用例1
测试用例1输入
1
3 9
AND 3 I1 I2 I3
OR 3 I1 I2 I3
AND 2 I1 I2
AND 2 I1 I3
AND 2 I2 I3
OR 2 O1 O7
AND 2 O2 O8
NOT 1 O9
OR 3 O3 O4 O5
8
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
2 6 9
2 6 9
2 6 9
2 6 9
2 6 9
2 6 9
2 6 9
2 6 9
测试用例1输出
0 0
1 0
1 0
0 1
1 0
0 1
0 1
1 1
测试用例2
测试用例2输入
1
3 8
NAND 2 O2 O3
NAND 2 I1 O4
NAND 2 I2 O4
NAND 2 I1 I2
NAND 2 O6 O7
NAND 2 O1 O8
NAND 2 I3 O8
NAND 2 O1 I3
8
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
1 5
1 5
1 5
1 5
1 5
1 5
1 5
1 5
测试用例2输出
0
1
1
0
1
0
0
1
测试用例3
测试用例3输入
1
1 2
AND 2 I1 O2
NOT 1 O1
2
0
1
0 0
2 1 2
测试用例3输出
LOOP
测试用例4
测试用例4输入
1
2 2
AND 2 I1 O2
AND 2 I2 O1
2
0 1
1 1
0 0
2 1 2
测试用例4输出
LOOP