{ }codelper
トポロジカルソートP0381

手がかりだけで決まる順位

1 番から n 番までの選手 n 人が競走に出て、同じ順位はない。
手がかりが m 個与えられ、手がかり "a b" は a 番の選手が b 番の選手より先にゴールしたという意味である。
同じ手がかりが何度も与えられることがあり、手がかり同士が食い違うこともある。
手がかりだけを見て、1 位から順に順位を決めていく。
まだ順位が決まっていない選手のうち、自分より先にゴールしたと書かれた選手が全員すでに順位が決まっている選手を候補と呼ぶ。
自分より先にゴールしたという手がかりが一つもない選手も候補である。
候補がちょうど一人なら、その選手が次の順位になり、残りの選手で同じことを繰り返す。
候補がいないか二人以上なら、そこで止まる。
全員の順位が決まっても止まる。
一行目に、順位が決まった選手の番号を 1 位から順に出力せよ。
二行目に、止まったときの候補の番号を小さい順に出力せよ。

入力
一行目に n と m が空白一つで区切られて与えられる。
続く m 行には各行に a と b が空白一つで区切られて与えられる。
1 ≤ n ≤ 50、0 ≤ m ≤ 200、1 ≤ a, b ≤ n、a ≠ b
出力
二行。
番号は空白一つで区切る。
順位が決まった選手がいなければ一行目に - だけ、止まったときに候補がいなければ二行目に - だけを出力する
例 1 · stdin
5 5
3 1
1 4
3 1
4 2
4 5
stdout
3 1 4
2 5
例 2 · stdin
3 3
1 2
2 3
3 2
stdout
1
-
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

候補を見つけて一人ずつ取り出すことが、そのままトポロジカルソート(カーンのアルゴリズム)です。
選手ごとに「自分より先だという手がかりのうち、その先の選手の順位がまだ決まっていないものの数」(waiting)を数えておけば、waiting が 0 の選手が候補です。
候補の順位を決めたら、その選手より後だと書かれた選手の waiting を 1 ずつ減らします。
普通のトポロジカルソートは候補が複数いればどれかを選んで最後まで進みますが、この問題は候補がちょうど一人のときだけ進みます。
選手は 50 人だけなので、キューなしで毎回全員を見ても十分です。
選手ごとの「後の選手リスト」の持ち方は言語ごとに違います。
Python [[] for _ in range(n + 1)]、JavaScript・TypeScript Array.from({ length: n + 1 }, () => [])、Java は new ArrayList<>() を n+1 回 add し、C# は new List<int>[n + 1] を作ってからマスごとに new List<int>() を入れます。
C++ の vector<vector<int>> after(n + 1) は最初からマスごとに別々の空の vector です。
C にはリストがないので、手がかりを二つの配列(from、to)にそのまま書いておき、毎回見ていきます。
Craykon は after.追加([]) を n+1 回行います。
罠その一:Python [[]] * (n + 1) と JavaScript new Array(n + 1).fill([]) は、一つのリストをすべてのマスが一緒に指します。
一つ目の例では 3 番の順位を決めるときにすべての手がかりの後の選手が一度に減って、3 と 1 2 4 5 になります。
罠その二:C# でマスごとの new を忘れるとマスは null で、最初の Add で NullReferenceException になって止まります。
罠その三:同じ手がかりが二回出るとき、後の選手は集合(set)で一度だけつなぎ、waiting は行ごとに数える、というのはいけません。
一つ目の例の 1 番は waiting が 2 から 1 までしか減らず、3 と - になります。
どちらも出た回数だけ入れて数えます。
罠その四:候補が複数のとき一番小さい番号を選んで進み続けると(普通のトポロジカルソート)、一つ目の例で 3 1 4 2 5 と - になります。
2 番と 5 番のどちらが先かは手がかりからは分かりません。
罠その五:順位が決まった選手に印をつけておかないと、その選手の waiting が 0 のまま残り、次も候補として数えられます。
一つ目の例で 3 と 1 3 になります。
罠その六:空のリストの書き方です。
Python " ".join([]) と JavaScript [].join(" ") は空文字列なので、- の代わりに空行が出ます(二つ目の例の二行目)。
数のリストのつなぎ方も違います。
Python は map(str, order) で文字列に変えないと join できず、JavaScript の join と C# の string.Join は数をそのまま受け取ります。
Java の String.join は文字列しか受け取らないので StringBuilder でつなぎ、C・C++ はループで書きます。
Craykon は コンソール.書く(order) のようにリストを書くと空白でつないで書きます。

Pythonmain.py · 26 行
import sys data = sys.stdin.read().split()n, m = int(data[0]), int(data[1])after = [[] for _ in range(n + 1)]  # after[a] = a보다 늦게 들어왔다고 적힌 선수들waiting = [0] * (n + 1)             # 아직 등수가 안 정해진 '앞선 선수' 단서의 수for i in range(m):    a, b = int(data[2 + 2 * i]), int(data[3 + 2 * i])    after[a].append(b)  # 같은 단서가 또 나오면 또 넣고    waiting[b] += 1     # 또 센다(둘이 늘 맞는다) placed = [False] * (n + 1)order = []while True:    cand = [v for v in range(1, n + 1) if not placed[v] and waiting[v] == 0]    if len(cand) != 1:  # 후보가 없거나 여럿이면 멈춘다        break    v = cand[0]    placed[v] = True    order.append(v)    for b in after[v]:        waiting[b] -= 1 # " ".join([])은 빈 문자열이라 빈 목록은 따로 "-"로 쓴다print(" ".join(map(str, order)) if order else "-")print(" ".join(map(str, cand)) if cand else "-")
JavaScriptmain.js · 26 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;// fill([])은 같은 배열 하나를 모든 칸에 넣으니 칸마다 새 배열을 만든다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 + 2 * i], b = data[3 + 2 * i];  after[a].push(b);  waiting[b]++;} const placed = new Array(n + 1).fill(false);const order = [];let cand = [];while (true) {  cand = [];  for (let v = 1; v <= n; v++) if (!placed[v] && waiting[v] === 0) cand.push(v);  if (cand.length !== 1) break; // 후보가 없거나 여럿이면 멈춘다  const v = cand[0];  placed[v] = true;  order.push(v);  for (const b of after[v]) waiting[b]--;} console.log(order.length ? order.join(" ") : "-");console.log(cand.length ? cand.join(" ") : "-");
TypeScriptmain.ts · 26 行
const data: number[] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;// fill([])은 같은 배열 하나를 모든 칸에 넣으니 칸마다 새 배열을 만든다const after: number[][] = Array.from({ length: n + 1 }, () => []); // after[a] = a보다 늦게 들어왔다고 적힌 선수들const waiting: number[] = new Array(n + 1).fill(0); // 아직 등수가 안 정해진 '앞선 선수' 단서의 수for (let i = 0; i < m; i++) {  const a = data[2 + 2 * i], b = data[3 + 2 * i];  after[a].push(b);  waiting[b]++;} const placed: boolean[] = new Array(n + 1).fill(false);const order: number[] = [];let cand: number[] = [];while (true) {  cand = [];  for (let v = 1; v <= n; v++) if (!placed[v] && waiting[v] === 0) cand.push(v);  if (cand.length !== 1) break; // 후보가 없거나 여럿이면 멈춘다  const v = cand[0];  placed[v] = true;  order.push(v);  for (const b of after[v]) waiting[b]--;} console.log(order.length ? order.join(" ") : "-");console.log(cand.length ? cand.join(" ") : "-");
JavaMain.java · 43 行
import java.util.*; public class Main {    // 수 목록을 빈칸으로 이어 쓴다(비었으면 "-")    static String show(List<Integer> list) {        if (list.isEmpty()) return "-";        StringBuilder sb = new StringBuilder();        for (int v : list) {            if (sb.length() > 0) sb.append(' ');            sb.append(v);        }        return sb.toString();    }     public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int n = in.nextInt(), m = in.nextInt();        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++) {            int a = in.nextInt(), b = in.nextInt();            after.get(a).add(b);            waiting[b]++;        }         boolean[] placed = new boolean[n + 1];        List<Integer> order = new ArrayList<>();        List<Integer> cand;        while (true) {            cand = new ArrayList<>();            for (int v = 1; v <= n; v++) if (!placed[v] && waiting[v] == 0) cand.add(v);            if (cand.size() != 1) break; // 후보가 없거나 여럿이면 멈춘다            int v = cand.get(0);            placed[v] = true;            order.add(v);            for (int b : after.get(v)) waiting[b]--;        }         System.out.println(show(order));        System.out.println(show(cand));    }}
C#Main.cs · 38 行
using System;using System.Collections.Generic; 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 after = new List<int>[n + 1]; // after[a] = a보다 늦게 들어왔다고 적힌 선수들        for (int v = 0; v <= n; v++) after[v] = new List<int>(); // 칸마다 new를 해야 한다(처음에는 null)        var waiting = new int[n + 1]; // 아직 등수가 안 정해진 '앞선 선수' 단서의 수        for (int i = 0; i < m; i++)        {            int a = int.Parse(data[2 + 2 * i]), b = int.Parse(data[3 + 2 * i]);            after[a].Add(b);            waiting[b]++;        }         var placed = new bool[n + 1];        var order = new List<int>();        List<int> cand;        while (true)        {            cand = new List<int>();            for (int v = 1; v <= n; v++) if (!placed[v] && waiting[v] == 0) cand.Add(v);            if (cand.Count != 1) break; // 후보가 없거나 여럿이면 멈춘다            int w = cand[0];            placed[w] = true;            order.Add(w);            foreach (int b in after[w]) waiting[b]--;        }         // string.Join은 수 목록도 바로 이어 준다        Console.WriteLine(order.Count > 0 ? string.Join(" ", order) : "-");        Console.WriteLine(cand.Count > 0 ? string.Join(" ", cand) : "-");    }}
C++main.cpp · 43 行
#include <iostream>#include <vector>using namespace std; // 수 목록을 빈칸으로 이어 쓴다(비었으면 "-")void show(const vector<int>& list) {    if (list.empty()) cout << "-";    for (size_t i = 0; i < list.size(); i++) {        if (i > 0) cout << ' ';        cout << list[i];    }    cout << '\n';} int main() {    int n, m;    cin >> n >> m;    vector<vector<int>> after(n + 1); // after[a] = a보다 늦게 들어왔다고 적힌 선수들(칸마다 따로 있는 빈 vector)    vector<int> waiting(n + 1, 0);    // 아직 등수가 안 정해진 '앞선 선수' 단서의 수    for (int i = 0; i < m; i++) {        int a, b;        cin >> a >> b;        after[a].push_back(b);        waiting[b]++;    }     vector<bool> placed(n + 1, false);    vector<int> order, cand;    while (true) {        cand.clear();        for (int v = 1; v <= n; v++)            if (!placed[v] && waiting[v] == 0) cand.push_back(v);        if (cand.size() != 1) break; // 후보가 없거나 여럿이면 멈춘다        int v = cand[0];        placed[v] = true;        order.push_back(v);        for (int b : after[v]) waiting[b]--;    }     show(order);    show(cand);    return 0;}
Cmain.c · 41 行
#include <stdio.h> #define MAXN 50#define MAXM 200 // 수 k개를 빈칸으로 이어 쓴다(없으면 "-")void show(const int *a, int k) {    if (k == 0) printf("-");    for (int i = 0; i < k; i++) printf(i ? " %d" : "%d", a[i]);    printf("\n");} int main(void) {    int n, m;    if (scanf("%d %d", &n, &m) != 2) return 0;    int from[MAXM], to[MAXM];       // 단서를 그대로 적어 둔다(같은 단서도 그대로)    int waiting[MAXN + 1] = {0};    // 아직 등수가 안 정해진 '앞선 선수' 단서의 수    for (int i = 0; i < m; i++) {        scanf("%d %d", &from[i], &to[i]);        waiting[to[i]]++;    }     int placed[MAXN + 1] = {0};    int order[MAXN], cand[MAXN];    int count = 0, k = 0;    while (1) {        k = 0;        for (int v = 1; v <= n; v++)            if (!placed[v] && waiting[v] == 0) cand[k++] = v;        if (k != 1) break; // 후보가 없거나 여럿이면 멈춘다        int v = cand[0];        placed[v] = 1;        order[count++] = v;        for (int i = 0; i < m; i++)            if (from[i] == v) waiting[to[i]]--;    }     show(order, count);    show(cand, k);    return 0;}
Craykonmain.cray · 57 行
// 수 목록을 빈칸으로 이어 쓴다(비었으면 "-")보여주기(list){    もし (list.個数 == 0)    {        コンソール.書く("-");    }    でなければ    {        コンソール.書く(list); // 목록을 쓰면 빈칸으로 이어 쓴다    }} 첫줄 = コンソール.数読み();n = 첫줄[0];m = 첫줄[1];after = []; // after[a] = a보다 늦게 들어왔다고 적힌 선수들繰り返す (v = 0; v <= n; v++){    after.追加([]); // 칸마다 새 목록을 만든다}waiting = 新リスト(n + 1, 0); // 아직 등수가 안 정해진 '앞선 선수' 단서의 수繰り返す (i = 0; i < m; i++){    쌍 = コンソール.数読み();    after[쌍[0]].追加(쌍[1]);    waiting[쌍[1]] += 1;} placed = 新リスト(n + 1, 偽);order = [];cand = [];間 (真){    cand = [];    繰り返す (v = 1; v <= n; v++)    {        もし (!placed[v] && waiting[v] == 0)        {            cand.追加(v);        }    }    もし (cand.個数 != 1)    {        抜ける; // 후보가 없거나 여럿이면 멈춘다    }    w = cand[0];    placed[w] = 真;    order.追加(w);    それぞれ (b の中 after[w])    {        waiting[b] -= 1;    }} 보여주기(order);보여주기(cand);