{ }codelper
너비 우선 탐색큐P0291

문이 하나씩 잠기는 박물관

박물관에 전시실 n개가 있고 1번부터 n번까지 번호가 붙어 있다. 전시실 사이에는 복도 m개가 있고, 복도 하나 "a b"는 a번 전시실과 b번 전시실을 잇는다. 복도는 양쪽으로 다닐 수 있고, 복도 하나를 지나는 데 1분이 걸린다. 같은 두 전시실을 잇는 복도가 여러 개 있을 수 있다. 문 닫을 준비로 전시실 입구가 하나씩 잠긴다. i번 전시실에는 tᵢ분까지 들어갈 수 있다.
딱 tᵢ분에 닿으면 들어갈 수 있고, 그보다 늦게 닿으면 들어갈 수 없다. 입구가 잠긴 뒤에도 안에 있던 사람은 언제든 나갈 수 있다. 관람객 한 명이 0분에 1번 전시실 안에 있다. 1번 전시실에는 처음부터 있으므로 t₁과 상관없이 0분에 들어간 것으로 친다. 관람객은 복도로만 움직이고, 어떤 전시실을 지나가려면 그 전시실에 들어가야 한다. 전시실마다, 관람객이 그 전시실에 들어갈 수 있는 가장 이른 시각을 구하라. 어떤 길로 가도 들어갈 수 없는 전시실의 값은 -1이다. 첫 줄에 들어갈 수 있는 전시실의 수를 출력하라.
1번 전시실도 센다. 둘째 줄에 1번부터 n번 전시실까지의 값을 차례로 빈칸 하나로 띄워 출력하라.

입력
첫 줄에 n과 m, 둘째 줄에 t₁ … tₙ, 다음 m줄에 줄마다 a와 b가 주어진다.
한 줄의 수들은 빈칸 하나로 나뉜다.
1 ≤ n ≤ 10,000, 0 ≤ m ≤ 20,000, 0 ≤ tᵢ ≤ 10,000, 1 ≤ a, b ≤ n, a ≠ b.
모두 정수다
출력
첫 줄에 들어갈 수 있는 전시실의 수, 둘째 줄에 전시실마다 가장 이른 시각(분) n개.
들어갈 수 없으면 -1
예시 1 · stdin
6 6
5 1 1 3 2 9
1 2
2 3
1 4
4 5
5 3
3 6
stdout
4
0 1 -1 1 2 -1
예시 2 · stdin
4 3
0 1 2 3
1 2
2 3
3 4
stdout
4
0 1 2 3
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

너비 우선 탐색(BFS)은 큐에서 꺼내는 차례가 곧 가까운 차례라서, 어떤 전시실에 처음 닿는 때가 가장 이른 때예요.
그때 이미 잠겨 있으면 나중에 와도 잠겨 있으니, 닿는 순간에 t ≤ tᵥ를 한 번만 검사하면 돼요.
첫 예시: 3번에는 2번을 거쳐 2분에 닿지만 t₃ = 1이라 못 들어가고(5번을 거치면 3분), 그래서 t₆ = 9로 넉넉한 6번도 갈 길이 없어요.
함정 하나: 문을 무시하고 거리부터 다 구한 뒤 거리 ≤ t인 전시실만 남기면 틀려요 — 잠긴 전시실을 지나서 간 거리가 섞여서 첫 예시의 6번이 3으로 나와요.
함정 둘: t < tᵥ로 쓰면 딱 tᵥ분에 닿는 전시실을 놓쳐요(둘째 예시).
함정 셋: 떠나는 전시실의 t를 보면 틀려요 — 잠긴 뒤에도 나갈 수는 있어요.
함정 넷: 큐 대신 스택(나중에 넣은 것부터)으로 꺼내면 깊이 우선 탐색이 되어 돌아가는 길로 먼저 닿고, 그 늦은 시각 때문에 문이 잠겨요.
함정 다섯: 복도는 양쪽으로 다니니 인접 목록의 양쪽에 넣어요.
함정 여섯: 첫 줄의 수에는 1번도 들어가요.
큐는 언어마다 달라요: Python은 collections.deque의 popleft()(리스트의 pop(0)은 뒤의 것을 모두 당겨서 느려요), Java는 ArrayDeque, C#은 Queue<int>, C++은 std::queue를 써요.
JavaScript·TypeScript 배열의 shift()도 뒤의 것을 당기니 배열에 push만 하고 머리 번호를 옮기며 읽어요.
C는 전시실마다 큐에 한 번만 들어가므로 n칸 배열과 머리·꼬리 번호면 돼요.
크레이콘도 목록에 넣기로 넣고 머리 번호를 옮기며, 이웃은 각각 (v 에서 halls[u])로 돌아요.

Pythonmain.py · 29 줄
import sysfrom collections import deque data = sys.stdin.read().split()n, m = int(data[0]), int(data[1])close = [0] + [int(x) for x in data[2:2 + n]]  # close[v] = v번 전시실에 들어갈 수 있는 마지막 시각 # 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)halls = [[] for _ in range(n + 1)]for i in range(m):    a, b = int(data[2 + n + 2 * i]), int(data[3 + n + 2 * i])    halls[a].append(b)    halls[b].append(a) # 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다arrive = [-1] * (n + 1)arrive[1] = 0  # 1번에는 처음부터 있으니 close[1]은 보지 않는다queue = deque([1])while queue:    u = queue.popleft()    t = arrive[u] + 1  # 이웃 전시실에 닿는 시각    for v in halls[u]:        # 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다        if arrive[v] == -1 and t <= close[v]:            arrive[v] = t            queue.append(v) print(sum(1 for v in range(1, n + 1) if arrive[v] != -1))print(" ".join(map(str, arrive[1:])))
JavaScriptmain.js · 35 줄
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const m = data[1];// close[v] = v번 전시실에 들어갈 수 있는 마지막 시각const close = [0];for (let v = 1; v <= n; v++) close.push(data[1 + v]); // 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)const halls = Array.from({ length: n + 1 }, () => []);for (let i = 0; i < m; i++) {  const a = data[2 + n + 2 * i];  const b = data[3 + n + 2 * i];  halls[a].push(b);  halls[b].push(a);} // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다const arrive = new Array(n + 1).fill(-1);arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다const queue = [1];// shift()는 뒤의 것을 모두 당겨서 느리다. 머리 번호 head를 옮긴다for (let head = 0; head < queue.length; head++) {  const u = queue[head];  const t = arrive[u] + 1; // 이웃 전시실에 닿는 시각  for (const v of halls[u]) {    // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다    if (arrive[v] === -1 && t <= close[v]) {      arrive[v] = t;      queue.push(v);    }  }} console.log(queue.length); // 큐에 한 번씩 들어간 전시실이 곧 들어갈 수 있는 전시실이다console.log(arrive.slice(1).join(" "));
TypeScriptmain.ts · 37 줄
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const m = data[1];// close[v] = v번 전시실에 들어갈 수 있는 마지막 시각const close: number[] = [0];for (let v = 1; v <= n; v++) close.push(data[1 + v]); // 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)const halls: number[][] = Array.from({ length: n + 1 }, (): number[] => []);for (let i = 0; i < m; i++) {  const a = data[2 + n + 2 * i];  const b = data[3 + n + 2 * i];  halls[a].push(b);  halls[b].push(a);} // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다const arrive: number[] = new Array<number>(n + 1).fill(-1);arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다const queue: number[] = [1];// shift()는 뒤의 것을 모두 당겨서 느리다. 머리 번호 head를 옮긴다for (let head = 0; head < queue.length; head++) {  const u = queue[head];  const t = arrive[u] + 1; // 이웃 전시실에 닿는 시각  for (const v of halls[u]) {    // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다    if (arrive[v] === -1 && t <= close[v]) {      arrive[v] = t;      queue.push(v);    }  }} console.log(queue.length); // 큐에 한 번씩 들어간 전시실이 곧 들어갈 수 있는 전시실이다console.log(arrive.slice(1).join(" "));
JavaMain.java · 54 줄
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken();        int n = (int) st.nval;        st.nextToken();        int m = (int) st.nval;        int[] close = new int[n + 1]; // close[v] = v번 전시실에 들어갈 수 있는 마지막 시각        for (int v = 1; v <= n; v++) {            st.nextToken();            close[v] = (int) st.nval;        }         // 인접 목록: halls.get(v) = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)        List<List<Integer>> halls = new ArrayList<>();        for (int v = 0; v <= n; v++) halls.add(new ArrayList<>());        for (int i = 0; i < m; i++) {            st.nextToken();            int a = (int) st.nval;            st.nextToken();            int b = (int) st.nval;            halls.get(a).add(b);            halls.get(b).add(a);        }         // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다        int[] arrive = new int[n + 1];        Arrays.fill(arrive, -1);        arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다        ArrayDeque<Integer> queue = new ArrayDeque<>();        queue.add(1);        int reached = 1;        while (!queue.isEmpty()) {            int u = queue.poll();            int t = arrive[u] + 1; // 이웃 전시실에 닿는 시각            for (int v : halls.get(u)) {                // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다                if (arrive[v] == -1 && t <= close[v]) {                    arrive[v] = t;                    queue.add(v);                    reached++;                }            }        }         StringBuilder sb = new StringBuilder();        sb.append(reached).append('\n');        for (int v = 1; v <= n; v++) sb.append(arrive[v]).append(v < n ? ' ' : '\n');        System.out.print(sb);    }}
C#Main.cs · 52 줄
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), m = int.Parse(data[1]);        var close = new int[n + 1]; // close[v] = v번 전시실에 들어갈 수 있는 마지막 시각        for (int v = 1; v <= n; v++) close[v] = int.Parse(data[1 + v]);         // 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)        var halls = new List<int>[n + 1];        for (int v = 0; v <= n; v++) halls[v] = new List<int>();        for (int i = 0; i < m; i++)        {            int a = int.Parse(data[2 + n + 2 * i]), b = int.Parse(data[3 + n + 2 * i]);            halls[a].Add(b);            halls[b].Add(a);        }         // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다        var arrive = new int[n + 1];        Array.Fill(arrive, -1);        arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다        var queue = new Queue<int>();        queue.Enqueue(1);        int reached = 1;        while (queue.Count > 0)        {            int u = queue.Dequeue();            int t = arrive[u] + 1; // 이웃 전시실에 닿는 시각            foreach (int v in halls[u])            {                // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다                if (arrive[v] == -1 && t <= close[v])                {                    arrive[v] = t;                    queue.Enqueue(v);                    reached++;                }            }        }         var sb = new StringBuilder();        sb.Append(reached).Append('\n');        for (int v = 1; v <= n; v++) sb.Append(arrive[v]).Append(v < n ? ' ' : '\n');        Console.Write(sb);    }}
C++main.cpp · 46 줄
#include <iostream>#include <queue>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, m;    cin >> n >> m;    vector<int> close(n + 1); // close[v] = v번 전시실에 들어갈 수 있는 마지막 시각    for (int v = 1; v <= n; v++) cin >> close[v];     // 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)    vector<vector<int>> halls(n + 1);    for (int i = 0; i < m; i++) {        int a, b;        cin >> a >> b;        halls[a].push_back(b);        halls[b].push_back(a);    }     // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다    vector<int> arrive(n + 1, -1);    arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다    queue<int> q;    q.push(1);    int reached = 1;    while (!q.empty()) {        int u = q.front();        q.pop();        int t = arrive[u] + 1; // 이웃 전시실에 닿는 시각        for (int v : halls[u]) {            // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다            if (arrive[v] == -1 && t <= close[v]) {                arrive[v] = t;                q.push(v);                reached++;            }        }    }     cout << reached << '\n';    for (int v = 1; v <= n; v++) cout << arrive[v] << (v < n ? ' ' : '\n');    return 0;}
Cmain.c · 52 줄
#include <stdio.h>#include <stdlib.h> int main(void) {    int n, m;    if (scanf("%d %d", &n, &m) != 2) return 0;    int *close = malloc((n + 1) * sizeof(int)); // close[v] = v번 전시실에 들어갈 수 있는 마지막 시각    for (int v = 1; v <= n; v++) scanf("%d", &close[v]);     // 인접 목록을 배열 셋으로: head[v]에서 시작해 next[e]로 따라가며 to[e]를 본다    // 복도 하나가 양쪽으로 다니는 길 둘이 되므로 칸이 2m개 든다    int *head = malloc((n + 1) * sizeof(int));    int *next = malloc((2 * m + 1) * sizeof(int));    int *to = malloc((2 * m + 1) * sizeof(int));    for (int v = 0; v <= n; v++) head[v] = -1;    for (int i = 0; i < m; i++) {        int a, b;        scanf("%d %d", &a, &b);        to[2 * i] = b;        next[2 * i] = head[a];        head[a] = 2 * i;        to[2 * i + 1] = a;        next[2 * i + 1] = head[b];        head[b] = 2 * i + 1;    }     // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다    int *arrive = malloc((n + 1) * sizeof(int));    int *queue = malloc((n + 1) * sizeof(int)); // 전시실마다 한 번만 들어가니 n칸이면 된다    for (int v = 0; v <= n; v++) arrive[v] = -1;    arrive[1] = 0; // 1번에는 처음부터 있으니 close[1]은 보지 않는다    int qh = 0, qt = 0;    queue[qt++] = 1;    while (qh < qt) {        int u = queue[qh++];        int t = arrive[u] + 1; // 이웃 전시실에 닿는 시각        for (int e = head[u]; e != -1; e = next[e]) {            int v = to[e];            // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다            if (arrive[v] == -1 && t <= close[v]) {                arrive[v] = t;                queue[qt++] = v;            }        }    }     printf("%d\n", qt); // 큐에 한 번씩 들어간 전시실의 수    for (int v = 1; v <= n; v++) printf(v > 1 ? " %d" : "%d", arrive[v]);    printf("\n");    free(close); free(head); free(next); free(to); free(arrive); free(queue);    return 0;}
Craykonmain.cray · 44 줄
첫줄 = 콘솔.수들읽기();n = 첫줄[0];m = 첫줄[1];// 목록은 10,000칸까지라 전시실 번호에서 1을 빼 0번부터 둔다// close[v] = v번 전시실에 들어갈 수 있는 마지막 시각close = 콘솔.수들읽기(); // 인접 목록: halls[v] = v번 전시실과 복도로 이어진 전시실들(양쪽으로 다닌다)halls = [];반복 (v = 0; v < n; v++){    halls.넣기([]);}반복 (i = 0; i < m; i++){    쌍 = 콘솔.수들읽기();    halls[쌍[0] - 1].넣기(쌍[1] - 1);    halls[쌍[1] - 1].넣기(쌍[0] - 1);} // 너비 우선 탐색(BFS): 큐에서 꺼내는 차례가 곧 도착 시각이 이른 차례다arrive = 새목록(n, -1);arrive[0] = 0; // 1번에는 처음부터 있으니 close는 보지 않는다queue = [0];// 앞에서 빼는 대신 머리 번호 head를 옮긴다head = 0;동안 (head < queue.개수){    u = queue[head];    head++;    t = arrive[u] + 1; // 이웃 전시실에 닿는 시각    각각 (v 에서 halls[u])    {        // 처음 닿는 때가 가장 이른 때다. 그때 이미 잠겼으면 나중에 와도 잠겨 있다        만약 (arrive[v] == -1 && t <= close[v])        {            arrive[v] = t;            queue.넣기(v);        }    }} 콘솔.쓰기(queue.개수); // 큐에 한 번씩 들어간 전시실의 수콘솔.쓰기(arrive);