{ }codelper
空值 nullP0286

查三次找到电源箱负责人

露营地有 n 位客人、m 个营位、k 个电源箱,各自从 1 开始编号。
有三张表。
第一张表写着每位客人被分配的营位编号。
第二张表写着每个营位连接的电源箱编号。
第三张表写着每个电源箱的负责员工的名字。
三张表中,还没有确定的格子里不写编号或名字,而是写 -(一个减号)。
接下来给出 q 次停电的客人编号。
每次都按 客人 → 营位 → 电源箱 → 负责员工 的顺序查表,如果一直查到最后,就输出负责员工的名字。
如果途中遇到 -,就在那里停下,输出 - 和断开的步骤编号,中间用一个空格隔开。
客人没有营位是第 1 步,该营位没有电源箱是第 2 步,该电源箱没有负责员工是第 3 步。
多位客人可以使用同一个营位,多个营位也可以连接同一个电源箱。

输入
第一行给出 n、m、k、q,用一个空格隔开(1 ≤ n, m, k, q ≤ 100)。
第二行从 1 号客人起依次给出 n 个营位编号(1 到 m)或 -,第三行从 1 号营位起依次给出 m 个电源箱编号(1 到 k)或 -,第四行从 1 号电源箱起依次给出 k 个负责员工的名字(1~8 个英文小写字母)或 -,各项之间用一个空格隔开。
接下来 q 行每行给出一个客人编号(1 到 n)。
输出
q 行。
每行是负责员工的名字,或者 "- 步骤编号"(步骤编号是 1、2、3 之一)
样例 1 · stdin
5 4 3 6
2 - 4 1 3
1 3 - 2
dana - miro
1
2
3
4
5
1
stdout
miro
- 1
- 3
dana
- 2
miro
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

这道题把查到的值再当作下一张表的下标,所以把“没有”当下标用会怎样,各语言不一样。
存放“没有”的方式:Python None,JS·TS null,Java 用 Integer 和 String 的 null(int 放不了),C# int?,C++ std::optional<int>,Craykon 空。
C 的 int 没有“没有”,所以把不会用作编号的 0 约定为“没有”;名字是指针,可以用 NULL。
把“没有”当下标:Python box_of[None] 是 TypeError,Java 在把 null 的 Integer 当下标或放进 int 的那一刻抛出 NullPointerException,Craykon 在 boxOf[空] 处停止程序(用 空 做计算也会停止)。
TS 在严格模式(strict)下用 number | null 当下标是编译错误(TS2538),所以不可能漏掉 === null 的检查;C# 的 int? 也不能直接当下标,要用 .Value 取出。
相反,JS 的 boxOf[null] 不报错,返回 undefined。
所以无论哪种语言,都按“查 → 检查是否没有 → 再查下一个”的顺序写。
也有让“没有”一直传到最后的工具。
Java Optional.ofNullable(siteOf[g]).map(s -> boxOf[s]).map(b -> staffOf[b]) 中途出现 null 就以空的 Optional 结束。
但它不会告诉你在哪一步断开,所以这道题要每一步都检查。
JS·TS·C# 的 ?. 只防点号前面的东西不存在,防不了方括号里的下标不存在。
七个陷阱:① 在 JS 中先把三个值都查出来,前面的步骤为空时,后面的值不是 null 而是 undefined。
=== null 抓不住它(== null 两个都能抓住)。
② 在 Python 中把“没有”记成 -1,box_of[-1] 会不报错地读到最后一格。
③ 在 Java 中像 int site = siteOf[g] 这样把 null 放进 int,不会变成 0,而是抛出异常。
④ C# 的 GetValueOrDefault() 把 null 变成 0,于是跳过了为空的步骤,看起来像是在下一步断开。
⑤ 在 C 中如果 0 号格也是“没有”(0),“没有”会一直传到最后。
从后往前检查,不管在哪里断开都会回答第 3 步,所以要从前面的步骤开始检查。
⑥ 在 C++ 中对空的 optional 调用 .value() 会抛出 std::bad_optional_access 异常,用 * 则是未定义行为。
⑦ 在 Craykon 中把查表写在检查之前,程序会在把 空 当下标的那一行停止。

Pythonmain.py · 25 行
def read_row(convert):    # "-"는 None으로, 나머지는 convert로 바꾼다. 맨 앞에 None을 하나 두어 번호 1이 1번 칸이 되게 한다    return [None] + [None if word == "-" else convert(word) for word in input().split()]  n, m, k, q = map(int, input().split())site_of = read_row(int)   # 손님 → 자리box_of = read_row(int)    # 자리 → 전기함staff_of = read_row(str)  # 전기함 → 담당자 for _ in range(q):    guest = int(input())    site = site_of[guest]    if site is None:        print("- 1")        continue    box = box_of[site]  # site가 None이면 box_of[None]은 TypeError라서, 위에서 먼저 걸렀다    if box is None:        print("- 2")        continue    staff = staff_of[box]    if staff is None:        print("- 3")    else:        print(staff)
JavaScriptmain.js · 28 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0].split(" ")[3]); // "-"는 null로 바꾼다. 맨 앞에 null을 하나 두어 번호 1이 1번 칸이 되게 한다function readRow(line, convert) {  return [null, ...line.split(" ").map((word) => (word === "-" ? null : convert(word)))];}const siteOf = readRow(lines[1], Number); // 손님 → 자리const boxOf = readRow(lines[2], Number); // 자리 → 전기함const staffOf = readRow(lines[3], String); // 전기함 → 담당자 const out = [];for (let i = 0; i < q; i++) {  const guest = Number(lines[4 + i]);  const site = siteOf[guest];  if (site === null) {    out.push("- 1");    continue;  }  const box = boxOf[site]; // boxOf[null]은 오류 없이 undefined가 되므로, 위에서 먼저 걸렀다  if (box === null) {    out.push("- 2");    continue;  }  const staff = staffOf[box];  out.push(staff === null ? "- 3" : staff);}console.log(out.join("\n"));
TypeScriptmain.ts · 30 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0].split(" ")[3]); // "-"는 null로 바꾼다. 맨 앞에 null을 하나 두어 번호 1이 1번 칸이 되게 한다function readRow<T>(line: string, convert: (word: string) => T): (T | null)[] {  return [null, ...line.split(" ").map((word) => (word === "-" ? null : convert(word)))];}const siteOf: (number | null)[] = readRow(lines[1], Number); // 손님 → 자리const boxOf: (number | null)[] = readRow(lines[2], Number); // 자리 → 전기함const staffOf: (string | null)[] = readRow(lines[3], String); // 전기함 → 담당자 const out: string[] = [];for (let i = 0; i < q; i++) {  const guest = Number(lines[4 + i]);  const site = siteOf[guest];  if (site === null) {    out.push("- 1");    continue;  }  // 여기서부터 site는 number다. 위의 검사를 빼면 boxOf[site]는 컴파일 오류(null은 번호로 쓸 수 없다)가 된다  const box = boxOf[site];  if (box === null) {    out.push("- 2");    continue;  }  const staff = staffOf[box];  out.push(staff === null ? "- 3" : staff);}console.log(out.join("\n"));
JavaMain.java · 45 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    // int에는 null을 담을 수 없어서 Integer를 쓴다. "-"는 null로 두고, 번호 1이 1번 칸이 되게 한 칸 밀어 담는다    static Integer[] readNumbers(String line) {        String[] words = line.split(" ");        Integer[] row = new Integer[words.length + 1];        for (int i = 0; i < words.length; i++) {            row[i + 1] = words[i].equals("-") ? null : Integer.valueOf(words[i]);        }        return row;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().split(" ")[3]);        Integer[] siteOf = readNumbers(in.readLine()); // 손님 → 자리        Integer[] boxOf = readNumbers(in.readLine()); // 자리 → 전기함        String[] words = in.readLine().split(" ");        String[] staffOf = new String[words.length + 1]; // 전기함 → 담당자        for (int i = 0; i < words.length; i++) {            staffOf[i + 1] = words[i].equals("-") ? null : words[i];        }         StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            int guest = Integer.parseInt(in.readLine().trim());            Integer site = siteOf[guest];            if (site == null) {                out.append("- 1\n");                continue;            }            Integer box = boxOf[site]; // site가 null이면 번호로 쓰는 순간 NullPointerException이 난다            if (box == null) {                out.append("- 2\n");                continue;            }            String staff = staffOf[box];            out.append(staff == null ? "- 3" : staff).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 51 行
using System;using System.Text; class Program{    // int? 는 '수 또는 null'이다. "-"는 null로 두고, 번호 1이 1번 칸이 되게 한 칸 밀어 담는다    static int?[] ReadNumbers(string line)    {        string[] words = line.Split(' ');        var row = new int?[words.Length + 1];        for (int i = 0; i < words.Length; i++)        {            row[i + 1] = words[i] == "-" ? (int?)null : int.Parse(words[i]);        }        return row;    }     static void Main()    {        int q = int.Parse(Console.ReadLine().Split(' ')[3]);        int?[] siteOf = ReadNumbers(Console.ReadLine()); // 손님 → 자리        int?[] boxOf = ReadNumbers(Console.ReadLine());  // 자리 → 전기함        string[] words = Console.ReadLine().Split(' ');        var staffOf = new string[words.Length + 1];      // 전기함 → 담당자        for (int i = 0; i < words.Length; i++)        {            staffOf[i + 1] = words[i] == "-" ? null : words[i];        }         var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            int guest = int.Parse(Console.ReadLine());            int? site = siteOf[guest];            if (site == null)            {                sb.Append("- 1\n");                continue;            }            int? box = boxOf[site.Value]; // int? 는 그대로 번호로 쓸 수 없다. 값이 있음을 확인한 뒤 .Value로 꺼낸다            if (box == null)            {                sb.Append("- 2\n");                continue;            }            string staff = staffOf[box.Value];            sb.Append(staff ?? "- 3").Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 45 行
#include <iostream>#include <optional>#include <string>#include <vector> // std::optional<int>는 '수가 있거나 비어 있거나'다. "-"는 빈 optional로 두고, 번호 1이 1번 칸이 되게 한다std::vector<std::optional<int>> readNumbers(int count) {    std::vector<std::optional<int>> row(count + 1);    for (int i = 1; i <= count; i++) {        std::string word;        std::cin >> word;        if (word != "-") row[i] = std::stoi(word);    }    return row;} int main() {    int n, m, k, q;    std::cin >> n >> m >> k >> q;    auto siteOf = readNumbers(n);  // 손님 → 자리    auto boxOf = readNumbers(m);   // 자리 → 전기함    std::vector<std::optional<std::string>> staffOf(k + 1);  // 전기함 → 담당자    for (int i = 1; i <= k; i++) {        std::string word;        std::cin >> word;        if (word != "-") staffOf[i] = word;    }    for (int i = 0; i < q; i++) {        int guest;        std::cin >> guest;        std::optional<int> site = siteOf[guest];        if (!site) {            std::cout << "- 1\n";            continue;        }        std::optional<int> box = boxOf[*site];  // 비어 있는 optional에 *를 쓰면 정의되지 않은 동작이라, 먼저 확인했다        if (!box) {            std::cout << "- 2\n";            continue;        }        const std::optional<std::string>& staff = staffOf[*box];        if (!staff) std::cout << "- 3\n";        else std::cout << *staff << '\n';    }}
Cmain.c · 44 行
#include <stdio.h>#include <stdlib.h>#include <string.h> /* C의 int에는 '없음'이 없다. 번호가 1부터라서 0을 '없음'으로 정해 쓴다 */static void read_numbers(int *row, int count) {    char word[16];    for (int i = 1; i <= count; i++) {        scanf("%15s", word);        row[i] = strcmp(word, "-") == 0 ? 0 : atoi(word);    }} int main(void) {    int n, m, k, q;    scanf("%d %d %d %d", &n, &m, &k, &q);    static int site_of[101], box_of[101];    static char names[101][16];    static const char *staff_of[101]; /* 포인터에는 '없음'이 있다: NULL */    read_numbers(site_of, n);    read_numbers(box_of, m);    for (int i = 1; i <= k; i++) {        scanf("%15s", names[i]);        staff_of[i] = strcmp(names[i], "-") == 0 ? NULL : names[i];    }    for (int i = 0; i < q; i++) {        int guest;        scanf("%d", &guest);        int site = site_of[guest];        if (site == 0) {            puts("- 1");            continue;        }        int box = box_of[site];        if (box == 0) {            puts("- 2");            continue;        }        const char *staff = staff_of[box];        if (staff == NULL) puts("- 3"); /* NULL을 puts에 넘기면 정의되지 않은 동작이다 */        else puts(staff);    }    return 0;}
Craykonmain.cray · 59 行
// "-"는 빈값으로 바꾼다. 맨 앞에 빈값을 하나 두어 번호 1이 1번 칸이 되게 한다수표읽기(){    표 = [空];    遍历 (낱말 在 控制台.读行().分割(" "))    {        如果 (낱말 == "-")        {            표.添加(空);        }        否则        {            표.添加(낱말.转数字());        }    }    返回 표;} q = 控制台.读行().分割(" ")[3].转数字();자리표 = 수표읽기(); // 손님 → 자리전기함표 = 수표읽기(); // 자리 → 전기함담당표 = [空]; // 전기함 → 담당자遍历 (낱말 在 控制台.读行().分割(" ")){    如果 (낱말 == "-")    {        담당표.添加(空);    }    否则    {        담당표.添加(낱말);    }} 循环 (i = 0; i < q; i++){    손님 = 控制台.读行().转数字();    자리 = 자리표[손님];    如果 (자리 == 空)    {        控制台.输出("- 1");        继续;    }    전기함 = 전기함표[자리]; // 자리가 빈값이면 여기서 프로그램이 멈추므로, 위에서 먼저 걸렀다    如果 (전기함 == 空)    {        控制台.输出("- 2");        继续;    }    담당 = 담당표[전기함];    如果 (담당 == 空)    {        控制台.输出("- 3");    }    否则    {        控制台.输出(담당);    }}