어느 학과에 1번부터 n번까지 과목 n개가 있고, 학기는 1학기, 2학기, 3학기…로 센다.
과목마다 열리는 학기가 정해져 있다.
종류가 0인 과목은 모든 학기에, 1인 과목은 홀수 학기(1, 3, 5…)에만, 2인 과목은 짝수 학기(2, 4, 6…)에만 열린다.
선수 조건 m개가 주어지며, 조건 하나 "a b"는 a번 과목을 들은 학기가 끝난 뒤에야 b번 과목을 들을 수 있다는 뜻이다.
같은 조건이 여러 번 주어질 수도 있다.
한 학기에 듣는 과목 수에는 제한이 없고, 모든 과목은 들을 수 있는 가장 이른 학기에 듣는다.
곧, 한 과목의 선수 과목들을 듣는 학기 가운데 가장 늦은 학기를 L이라 하면(선수 과목이 없으면 L = 0), 그 과목은 L보다 뒤의 학기 가운데 그 과목이 열리는 가장 이른 학기에 듣는다.
조건이 고리를 이루어 영원히 들을 수 없는 과목이 있을 수 있다.
고리 안에 있는 과목과, 그런 과목을 먼저 들어야 하는 과목은 들을 수 없다.
첫 줄에 n과 m, 둘째 줄에 1번부터 n번 과목의 종류 n개가 빈칸으로 나뉘어 주어진다.
다음 m줄에는 줄마다 a와 b가 빈칸으로 나뉘어 주어진다.
첫째 줄에 1번부터 n번 과목을 듣는 학기를 빈칸으로 나누어 출력하라. 들을 수 없는 과목은 -1로 출력한다.
둘째 줄에 들을 수 있는 과목을 모두 듣는 데 필요한 학기 수, 곧 첫째 줄에서 가장 큰 학기를 출력하라. 들을 수 있는 과목이 하나도 없으면 0을 출력한다.
입력
1 ≤ n ≤ 10,000, 0 ≤ m ≤ 10,000, 과목의 종류는 0, 1, 2 가운데 하나, 1 ≤ a, b ≤ n, a ≠ b
출력
두 줄: 과목마다 학기(들을 수 없으면 -1), 필요한 학기 수
예시 1 · stdin
7 6
2 0 1 0 0 0 2
1 2
2 3
1 3
4 5
5 4
5 6
stdout
2 3 5 -1 -1 -1 2
5
main.py
오류 목록00F8 ↓ · ⇧F8 ↑
입력(stdin)
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
선수 과목이 아직 몇 개 남았는지(waiting)를 세어 두고, 0이 된 과목을 큐에 넣어 차례로 꺼내는 위상 정렬(칸 알고리즘)로 풀어요. 과목을 꺼낼 때는 선수 과목을 모두 들은 뒤라 학기를 정할 수 있어요. 선수 과목만 보고 모은 가장 이른 학기 earliest에서, 그 과목이 열리지 않는 학기이면 한 학기 미뤄요(홀짝은 t % 2로 물어요). 그다음 뒤 과목마다 earliest를 max(지금 값, 이 과목 학기 + 1)로 올려요. 큐는 언어마다 달라요: Python collections.deque의 popleft()(목록의 pop(0)은 매번 앞을 당겨 느려요), Java ArrayDeque, C# Queue<int>, C++ std::queue가 있고, JS·TS에는 큐가 없어 shift()도 느리니 배열에 넣기만 하고 머리 번호 head를 옮겨요. C는 배열과 머리·꼬리 번호로, 크레이콘도 목록에 넣기만 하고 head를 옮겨요. 과목마다 뒤 과목 목록을 두는 방법도 달라요: Python [[] for _ in range(n + 1)]([[]] * (n + 1)은 같은 목록 하나를 n+1번 가리켜요), Java List<List<Integer>>, C는 head·next·to 배열 셋으로 이어요. 함정 다섯: ① 처음 닿은 선수 과목으로 학기를 정하면 틀려요. 1→2→3과 1→3이 있으면 3번은 2번 과목 뒤에 들어야 해요. 늘 가장 늦은 선수 과목을 봐야 해요. ② 학기를 0으로 채워 두면 고리 때문에 끝내 꺼내지 못한 과목이 0으로 남아요. -1로 채워 두거나 따로 물어요. ③ 선수 과목을 거슬러 올라가는 재귀로 풀면 고리에서 끝없이 돌고, 10,000단계 사슬에서는 Python 재귀 한도(기본 1,000)를 넘어 멈춰요(크레이콘도 약 3,000단계까지예요). ④ 같은 조건이 겹칠 때 뒤 과목 목록은 집합으로 한 번만 잇고 waiting은 줄마다 세면, 그 과목은 waiting이 0이 되지 않아 -1이 돼요. 둘을 같은 방식으로 세요. ⑤ 열리는 학기에 맞춰 미룬 값을 뒤 과목에 넘겨야 해요. 열리는 학기를 빼고 학기를 다 구한 뒤 마지막에 과목마다 홀짝만 맞추면, 미뤄진 과목 뒤의 과목이 한 학기 이르게 나와요. 선수 과목이 없는 과목도 종류가 2이면 1학기가 아니라 2학기예요.
Pythonmain.py · 36 줄
import sysfrom collections import dequedef first_open(t, kind): """t학기부터 세어 그 과목이 열리는 가장 이른 학기""" if (kind == 1 and t % 2 == 0) or (kind == 2 and t % 2 == 1): return t + 1 return tdata = sys.stdin.buffer.read().split()n, m = int(data[0]), int(data[1])kind = [0] + [int(x) for x in data[2:2 + n]] # 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기after = [[] for _ in range(n + 1)] # after[a] = a를 들어야 들을 수 있는 과목들waiting = [0] * (n + 1) # 아직 듣지 않은 선수 과목의 수for i in range(m): a, b = int(data[2 + n + 2 * i]), int(data[3 + n + 2 * i]) after[a].append(b) waiting[b] += 1earliest = [1] * (n + 1) # 선수 과목만 보면 들을 수 있는 가장 이른 학기term = [-1] * (n + 1) # 끝까지 -1이면 고리 때문에 들을 수 없다q = deque(v for v in range(1, n + 1) if waiting[v] == 0)while q: a = q.popleft() term[a] = first_open(earliest[a], kind[a]) # 선수 과목을 다 들었으니 이제 학기가 정해진다 for b in after[a]: # 선수 과목 가운데 가장 늦은 것의 다음 학기 earliest[b] = max(earliest[b], term[a] + 1) waiting[b] -= 1 if waiting[b] == 0: q.append(b)print(" ".join(map(str, term[1:])))print(max([0] + term[1:])) # 들을 수 있는 과목이 없으면 0
JavaScriptmain.js · 35 줄
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;const kind = [0, ...data.slice(2, 2 + n)]; // 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기// t학기부터 세어 그 과목이 열리는 가장 이른 학기function firstOpen(t, k) { if ((k === 1 && t % 2 === 0) || (k === 2 && t % 2 === 1)) return t + 1; return t;}const after = Array.from({ length: n + 1 }, () => []); // after[a] = a를 들어야 들을 수 있는 과목들const waiting = new Array(n + 1).fill(0); // 아직 듣지 않은 선수 과목의 수for (let i = 0; i < m; i++) { const a = data[2 + n + 2 * i], b = data[3 + n + 2 * i]; after[a].push(b); waiting[b]++;}const earliest = new Array(n + 1).fill(1); // 선수 과목만 보면 들을 수 있는 가장 이른 학기const term = new Array(n + 1).fill(-1); // 끝까지 -1이면 고리 때문에 들을 수 없다const queue = [];for (let v = 1; v <= n; v++) if (waiting[v] === 0) queue.push(v);// shift()는 매번 앞을 당겨 느리니 머리 번호 head로 꺼낸다for (let head = 0; head < queue.length; head++) { const a = queue[head]; term[a] = firstOpen(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 for (const b of after[a]) { earliest[b] = Math.max(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] === 0) queue.push(b); }}const terms = term.slice(1);console.log(terms.join(" "));console.log(terms.reduce((x, y) => Math.max(x, y), 0));
TypeScriptmain.ts · 36 줄
import { readFileSync } from "node:fs";const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0], m = data[1];const kind: number[] = [0, ...data.slice(2, 2 + n)]; // 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기// t학기부터 세어 그 과목이 열리는 가장 이른 학기function firstOpen(t: number, k: number): number { if ((k === 1 && t % 2 === 0) || (k === 2 && t % 2 === 1)) return t + 1; return t;}const after: number[][] = Array.from({ length: n + 1 }, () => []); // after[a] = a를 들어야 들을 수 있는 과목들const waiting: number[] = new Array<number>(n + 1).fill(0); // 아직 듣지 않은 선수 과목의 수for (let i = 0; i < m; i++) { const a = data[2 + n + 2 * i], b = data[3 + n + 2 * i]; after[a].push(b); waiting[b]++;}const earliest: number[] = new Array<number>(n + 1).fill(1); // 선수 과목만 보면 들을 수 있는 가장 이른 학기const term: number[] = new Array<number>(n + 1).fill(-1); // 끝까지 -1이면 고리 때문에 들을 수 없다const queue: number[] = [];for (let v = 1; v <= n; v++) if (waiting[v] === 0) queue.push(v);for (let head = 0; head < queue.length; head++) { const a = queue[head]; term[a] = firstOpen(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 for (const b of after[a]) { earliest[b] = Math.max(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] === 0) queue.push(b); }}const terms = term.slice(1);console.log(terms.join(" "));console.log(terms.reduce((x, y) => Math.max(x, y), 0));
JavaMain.java · 61 줄
import java.io.*;import java.util.*;public class Main { // t학기부터 세어 그 과목이 열리는 가장 이른 학기(kind 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기) static int firstOpen(int t, int kind) { if ((kind == 1 && t % 2 == 0) || (kind == 2 && t % 2 == 1)) return t + 1; return t; } 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[] kind = new int[n + 1]; for (int v = 1; v <= n; v++) { st.nextToken(); kind[v] = (int) st.nval; } List<List<Integer>> after = new ArrayList<>(); // after.get(a) = a를 들어야 들을 수 있는 과목들 for (int v = 0; v <= n; v++) after.add(new ArrayList<>()); int[] waiting = new int[n + 1]; // 아직 듣지 않은 선수 과목의 수 for (int i = 0; i < m; i++) { st.nextToken(); int a = (int) st.nval; st.nextToken(); int b = (int) st.nval; after.get(a).add(b); waiting[b]++; } int[] earliest = new int[n + 1]; // 선수 과목만 보면 들을 수 있는 가장 이른 학기 Arrays.fill(earliest, 1); int[] term = new int[n + 1]; Arrays.fill(term, -1); // 끝까지 -1이면 고리 때문에 들을 수 없다 ArrayDeque<Integer> q = new ArrayDeque<>(); for (int v = 1; v <= n; v++) { if (waiting[v] == 0) q.add(v); } while (!q.isEmpty()) { int a = q.poll(); term[a] = firstOpen(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 for (int b : after.get(a)) { earliest[b] = Math.max(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] == 0) q.add(b); } } StringBuilder sb = new StringBuilder(); int most = 0; for (int v = 1; v <= n; v++) { if (v > 1) sb.append(' '); sb.append(term[v]); most = Math.max(most, term[v]); } sb.append('\n').append(most).append('\n'); System.out.print(sb); }}
C#Main.cs · 61 줄
using System;using System.Collections.Generic;using System.Text;class Program{ // t학기부터 세어 그 과목이 열리는 가장 이른 학기(kind 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기) static int FirstOpen(int t, int kind) { if ((kind == 1 && t % 2 == 0) || (kind == 2 && t % 2 == 1)) return t + 1; return t; } 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 kind = new int[n + 1]; for (int v = 1; v <= n; v++) kind[v] = int.Parse(data[1 + v]); var after = new List<int>[n + 1]; // after[a] = a를 들어야 들을 수 있는 과목들 for (int v = 0; v <= n; v++) after[v] = new List<int>(); var waiting = new int[n + 1]; // 아직 듣지 않은 선수 과목의 수 for (int i = 0; i < m; i++) { int a = int.Parse(data[2 + n + 2 * i]), b = int.Parse(data[3 + n + 2 * i]); after[a].Add(b); waiting[b]++; } var earliest = new int[n + 1]; // 선수 과목만 보면 들을 수 있는 가장 이른 학기 Array.Fill(earliest, 1); var term = new int[n + 1]; Array.Fill(term, -1); // 끝까지 -1이면 고리 때문에 들을 수 없다 var q = new Queue<int>(); for (int v = 1; v <= n; v++) { if (waiting[v] == 0) q.Enqueue(v); } while (q.Count > 0) { int a = q.Dequeue(); term[a] = FirstOpen(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 foreach (int b in after[a]) { earliest[b] = Math.Max(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] == 0) q.Enqueue(b); } } var sb = new StringBuilder(); int most = 0; for (int v = 1; v <= n; v++) { if (v > 1) sb.Append(' '); sb.Append(term[v]); most = Math.Max(most, term[v]); } sb.Append('\n').Append(most).Append('\n'); Console.Write(sb); }}
C++main.cpp · 50 줄
#include <algorithm>#include <iostream>#include <queue>#include <vector>// t학기부터 세어 그 과목이 열리는 가장 이른 학기(kind 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기)int firstOpen(int t, int kind) { if ((kind == 1 && t % 2 == 0) || (kind == 2 && t % 2 == 1)) return t + 1; return t;}int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n, m; std::cin >> n >> m; std::vector<int> kind(n + 1); for (int v = 1; v <= n; v++) std::cin >> kind[v]; std::vector<std::vector<int>> after(n + 1); // after[a] = a를 들어야 들을 수 있는 과목들 std::vector<int> waiting(n + 1, 0); // 아직 듣지 않은 선수 과목의 수 for (int i = 0; i < m; i++) { int a, b; std::cin >> a >> b; after[a].push_back(b); waiting[b]++; } std::vector<int> earliest(n + 1, 1); // 선수 과목만 보면 들을 수 있는 가장 이른 학기 std::vector<int> term(n + 1, -1); // 끝까지 -1이면 고리 때문에 들을 수 없다 std::queue<int> q; for (int v = 1; v <= n; v++) { if (waiting[v] == 0) q.push(v); } while (!q.empty()) { int a = q.front(); q.pop(); term[a] = firstOpen(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 for (int b : after[a]) { earliest[b] = std::max(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] == 0) q.push(b); } } int most = 0; for (int v = 1; v <= n; v++) { std::cout << (v > 1 ? " " : "") << term[v]; most = std::max(most, term[v]); } std::cout << "\n" << most << "\n";}
Cmain.c · 59 줄
#include <stdio.h>#include <stdlib.h>// t학기부터 세어 그 과목이 열리는 가장 이른 학기(kind 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기)int first_open(int t, int kind) { if ((kind == 1 && t % 2 == 0) || (kind == 2 && t % 2 == 1)) return t + 1; return t;}int main(void) { int n, m; if (scanf("%d %d", &n, &m) != 2) return 0; int *kind = malloc((n + 1) * sizeof(int)); for (int v = 1; v <= n; v++) scanf("%d", &kind[v]); // 이음 목록을 배열 셋으로: head[a]에서 시작해 next[e]로 따라가며 to[e]를 본다 int *head = malloc((n + 1) * sizeof(int)); int *next = malloc((m + 1) * sizeof(int)); int *to = malloc((m + 1) * sizeof(int)); int *waiting = calloc(n + 1, sizeof(int)); // 아직 듣지 않은 선수 과목의 수 int *earliest = malloc((n + 1) * sizeof(int)); // 선수 과목만 보면 들을 수 있는 가장 이른 학기 int *term = malloc((n + 1) * sizeof(int)); int *queue = malloc((n + 1) * sizeof(int)); for (int v = 0; v <= n; v++) { head[v] = -1; earliest[v] = 1; term[v] = -1; // 끝까지 -1이면 고리 때문에 들을 수 없다 } for (int e = 0; e < m; e++) { int a, b; scanf("%d %d", &a, &b); to[e] = b; next[e] = head[a]; head[a] = e; waiting[b]++; } int qh = 0, qt = 0; for (int v = 1; v <= n; v++) { if (waiting[v] == 0) queue[qt++] = v; } while (qh < qt) { int a = queue[qh++]; term[a] = first_open(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 for (int e = head[a]; e != -1; e = next[e]) { int b = to[e]; if (term[a] + 1 > earliest[b]) earliest[b] = term[a] + 1; // 가장 늦은 선수 과목의 다음 학기 if (--waiting[b] == 0) queue[qt++] = b; } } int most = 0; for (int v = 1; v <= n; v++) { printf(v > 1 ? " %d" : "%d", term[v]); if (term[v] > most) most = term[v]; } printf("\n%d\n", most); free(kind); free(head); free(next); free(to); free(waiting); free(earliest); free(term); free(queue); return 0;}
Craykonmain.cray · 64 줄
// t학기부터 세어 그 과목이 열리는 가장 이른 학기(종류 0: 모든 학기, 1: 홀수 학기, 2: 짝수 학기)열리는학기(t, 종류){ 만약 (종류 == 1 && t % 2 == 0 || 종류 == 2 && t % 2 == 1) { 돌려주기 t + 1; } 돌려주기 t;}첫줄 = 콘솔.수들읽기();n = 첫줄[0];m = 첫줄[1];// 목록은 10,000칸까지라 과목 번호에서 1을 빼 0번부터 둔다kind = 콘솔.수들읽기();after = []; // after[a] = a를 들어야 들을 수 있는 과목들반복 (v = 0; v < n; v++){ after.넣기([]);}waiting = 새목록(n, 0); // 아직 듣지 않은 선수 과목의 수반복 (i = 0; i < m; i++){ 쌍 = 콘솔.수들읽기(); after[쌍[0] - 1].넣기(쌍[1] - 1); waiting[쌍[1] - 1] += 1;}earliest = 새목록(n, 1); // 선수 과목만 보면 들을 수 있는 가장 이른 학기term = 새목록(n, -1); // 끝까지 -1이면 고리 때문에 들을 수 없다queue = [];반복 (v = 0; v < n; v++){ 만약 (waiting[v] == 0) { queue.넣기(v); }}// 앞에서 꺼내는 대신 머리 번호 head를 옮긴다head = 0;동안 (head < queue.개수){ a = queue[head]; head++; term[a] = 열리는학기(earliest[a], kind[a]); // 선수 과목을 다 들었으니 이제 학기가 정해진다 각각 (b 에서 after[a]) { earliest[b] = 수학.큰수(earliest[b], term[a] + 1); // 가장 늦은 선수 과목의 다음 학기 waiting[b] -= 1; 만약 (waiting[b] == 0) { queue.넣기(b); } }}most = 0;각각 (t 에서 term){ most = 수학.큰수(most, t);}콘솔.쓰기(term);콘솔.쓰기(most);