{ }codelper
グラフ(隣接リスト)キューP0278

見ていた人が去ると抜けるグループチャット

グループチャットに 1 番から n 番まで n 人がいる。
人はそれぞれ、チャットの中のほかの何人かを見ている。
「a b」は a が b を見ているという意味で、b が a を見ているかどうかとは関係ない。
i 番の人には基準 tᵢ がある。
毎日その日の朝、チャットにいる人はそれぞれ、自分が見ている人のうちまだチャットにいる人の数を数える。
その数が基準 tᵢ より小さい人は、みなその日いっしょにチャットを抜ける。
同じ日に抜ける人どうしは、互いを見て抜けるのではない。
その日の朝に数えた数だけで決める。
抜けた人は二度と戻らない。
初日の朝は全員がチャットにいて、誰も抜けない日が来れば、その後も誰も抜けない。
一行目に、最後まで残る人の数を出力せよ。
二行目に、1 番から n 番まで人ごとにチャットを抜けた日(初日が 1)を出力し、最後まで残る人は 0 を出力せよ。

入力
一行目に n と見る関係の数 m、二行目に t₁ … tₙ、続く m 行に「a b」が空白一つで区切られて与えられる。
1 ≤ n ≤ 10,000、0 ≤ m ≤ 30,000、0 ≤ tᵢ ≤ 10,000。
1 ≤ a, b ≤ n、a ≠ b で、同じ「a b」は二度出てこない(「a b」と「b a」がどちらも出てくることはある)
出力
二行:残る人の数と、人ごとに抜けた日 n 個(空白一つで区切る)
例 1 · stdin
6 6
1 2 1 0 1 2
1 2
2 1
2 3
3 5
6 4
6 1
stdout
1
4 3 2 0 1 5
main.py
入力(stdin)

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

$ 準備完了

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

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

毎日全員を数え直すと一日に m 回、日数は n 日まで(一列につながって見ていると 10,000 日)続き、3 億回で遅すぎます。
変わる人だけを見ます:watching[v] = v が見ている人のうち残っている数を持ち、今日抜けた u ごとに u を見ていた人たちの数だけを 1 ずつ減らします。
そのためには「a b」を followers[b] に a を入れて逆向きに保存しなければなりません — a のリストに b を入れると、u が見ていた人の数を減らすことになって間違えます。
数が初めて基準を下回った人は、その場で「翌日抜ける」と記して明日のリストに入れます(今日抜けた人が全員いなくなってから数えるのと同じです)。
一人は一度だけ入れなければならないので、すでに抜けたか抜けることが決まった人(day[w] != 0)には触れません — この確認がないと同じ人がリストに二度入り、その人を見ていた人たちの数が二度減ります。
日ごとにリストを分けても(今日・明日)、キュー一つに入れて day[w] = day[u] + 1 と書いてもかまいません(幅優先探索と同じ形)。
例:5 番(誰も見ていない、基準 1)が初日、5 番を見ていた 3 番が二日目、続いて 2 番・1 番・6 番が一日ずつ遅れて抜け、基準が 0 の 4 番だけが残ります。
落とし穴一:毎日 1 番から順に見てその場ですぐ抜けさせると、同じ日に前の番号が抜けたことを後ろの番号がその日のうちに感じて、日が早まります。
落とし穴二:基準と同じなら残ります(< で、<= ではありません)。
隣接リストは言語ごとに違います:Python [[] for _ in range(n)]([[]] * n は一つのリストを n 回指します)、JavaScript・TypeScript Array.from({ length: n }, () => [])(new Array(n).fill([]) も一つのリストを共有します)、Java List<List<Integer>>、C# List<int>[]、C++ std::vector<std::vector<int>>、C は大きさの決まった配列二つで「開始位置 + つなげたリスト」(CSR)を作ります。
Craykon はリストのリストを followers.追加([]) で n 回作ります — リストは 10,000 個までなので n も 10,000 までです。

Pythonmain.py · 33 行
import sys data = sys.stdin.buffer.read().split()n, m = int(data[0]), int(data[1])need = [int(x) for x in data[2:2 + n]]followers = [[] for _ in range(n)]  # followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장)watching = [0] * n                   # 지켜보는 사람 가운데 아직 방에 있는 수pos = 2 + nfor _ in range(m):    a, b = int(data[pos]) - 1, int(data[pos + 1]) - 1    pos += 2    followers[b].append(a)    watching[a] += 1 day = [0] * n  # 나간 날(0은 끝까지 남음)today = [v for v in range(n) if watching[v] < need[v]]for v in today:    day[v] = 1d = 1while today:    tomorrow = []    for u in today:              # 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다        for w in followers[u]:            if day[w] == 0:      # 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다                watching[w] -= 1                if watching[w] < need[w]:                    day[w] = d + 1   # 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다                    tomorrow.append(w)    today = tomorrow    d += 1 print(day.count(0))print(" ".join(map(str, day)))
JavaScriptmain.js · 36 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;const need = data.slice(2, 2 + n);const followers = Array.from({ length: n }, () => []); // followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장)const watching = new Array(n).fill(0); // 지켜보는 사람 가운데 아직 방에 있는 수for (let i = 0; i < m; i++) {  const a = data[2 + n + 2 * i] - 1;  const b = data[3 + n + 2 * i] - 1;  followers[b].push(a);  watching[a]++;} const day = new Array(n).fill(0); // 나간 날(0은 끝까지 남음)let today = [];for (let v = 0; v < n; v++) {  if (watching[v] < need[v]) {    day[v] = 1;    today.push(v);  }}for (let d = 1; today.length > 0; d++) {  const tomorrow = [];  for (const u of today) {    // 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다    for (const w of followers[u]) {      if (day[w] !== 0) continue; // 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다      watching[w]--;      if (watching[w] < need[w]) {        day[w] = d + 1; // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다        tomorrow.push(w);      }    }  }  today = tomorrow;}console.log(day.filter((x) => x === 0).length + "\n" + day.join(" "));
TypeScriptmain.ts · 38 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;const need: number[] = data.slice(2, 2 + n);const followers: number[][] = Array.from({ length: n }, () => []); // followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장)const watching: number[] = new Array<number>(n).fill(0); // 지켜보는 사람 가운데 아직 방에 있는 수for (let i = 0; i < m; i++) {  const a = data[2 + n + 2 * i] - 1;  const b = data[3 + n + 2 * i] - 1;  followers[b].push(a);  watching[a]++;} const day: number[] = new Array<number>(n).fill(0); // 나간 날(0은 끝까지 남음)let today: number[] = [];for (let v = 0; v < n; v++) {  if (watching[v] < need[v]) {    day[v] = 1;    today.push(v);  }}for (let d = 1; today.length > 0; d++) {  const tomorrow: number[] = [];  for (const u of today) {    // 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다    for (const w of followers[u]) {      if (day[w] !== 0) continue; // 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다      watching[w]--;      if (watching[w] < need[w]) {        day[w] = d + 1; // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다        tomorrow.push(w);      }    }  }  today = tomorrow;}console.log(day.filter((x) => x === 0).length + "\n" + day.join(" "));
JavaMain.java · 58 行
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[] need = new int[n];        for (int v = 0; v < n; v++) {            st.nextToken();            need[v] = (int) st.nval;        }        // followers.get(b) = b를 지켜보는 사람들(화살표를 거꾸로 저장)        List<List<Integer>> followers = new ArrayList<>();        for (int v = 0; v < n; v++) followers.add(new ArrayList<>());        int[] watching = new int[n]; // 지켜보는 사람 가운데 아직 방에 있는 수        for (int i = 0; i < m; i++) {            st.nextToken();            int a = (int) st.nval - 1;            st.nextToken();            int b = (int) st.nval - 1;            followers.get(b).add(a);            watching[a]++;        }         int[] day = new int[n]; // 나간 날(0은 끝까지 남음)        List<Integer> today = new ArrayList<>();        for (int v = 0; v < n; v++) {            if (watching[v] < need[v]) {                day[v] = 1;                today.add(v);            }        }        for (int d = 1; !today.isEmpty(); d++) {            List<Integer> tomorrow = new ArrayList<>();            for (int u : today) { // 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다                for (int w : followers.get(u)) {                    if (day[w] != 0) continue; // 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다                    if (--watching[w] < need[w]) {                        day[w] = d + 1; // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다                        tomorrow.add(w);                    }                }            }            today = tomorrow;        }        int stay = 0;        StringBuilder sb = new StringBuilder();        for (int v = 0; v < n; v++) {            if (day[v] == 0) stay++;            sb.append(day[v]).append(v + 1 < n ? ' ' : '\n');        }        System.out.print(stay + "\n" + sb);    }}
C#Main.cs · 51 行
using System;using System.Collections.Generic;using System.Linq; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        int n = data[0], m = data[1];        int[] need = data[2..(2 + n)];        var followers = new List<int>[n]; // followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장)        for (int v = 0; v < n; v++) followers[v] = new List<int>();        var watching = new int[n]; // 지켜보는 사람 가운데 아직 방에 있는 수        for (int i = 0; i < m; i++)        {            int a = data[2 + n + 2 * i] - 1, b = data[3 + n + 2 * i] - 1;            followers[b].Add(a);            watching[a]++;        }         var day = new int[n]; // 나간 날(0은 끝까지 남음)        var today = new List<int>();        for (int v = 0; v < n; v++)        {            if (watching[v] < need[v])            {                day[v] = 1;                today.Add(v);            }        }        for (int d = 1; today.Count > 0; d++)        {            var tomorrow = new List<int>();            foreach (int u in today) // 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다            {                foreach (int w in followers[u])                {                    if (day[w] != 0) continue; // 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다                    if (--watching[w] < need[w])                    {                        day[w] = d + 1; // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다                        tomorrow.Add(w);                    }                }            }            today = tomorrow;        }        Console.Write(day.Count(x => x == 0) + "\n" + string.Join(' ', day) + "\n");    }}
C++main.cpp · 41 行
#include <iostream>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, m;    std::cin >> n >> m;    std::vector<int> need(n);    for (int &x : need) std::cin >> x;    std::vector<std::vector<int>> followers(n); // followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장)    std::vector<int> watching(n, 0);            // 지켜보는 사람 가운데 아직 방에 있는 수    for (int i = 0; i < m; i++) {        int a, b;        std::cin >> a >> b;        followers[b - 1].push_back(a - 1);        watching[a - 1]++;    }     std::vector<int> day(n, 0); // 나간 날(0은 끝까지 남음)    std::vector<int> today;    for (int v = 0; v < n; v++)        if (watching[v] < need[v]) day[v] = 1, today.push_back(v);    int left = 0;    for (int d = 1; !today.empty(); d++) {        left += static_cast<int>(today.size());        std::vector<int> tomorrow;        for (int u : today) {           // 오늘 나간 사람 u를 지켜보던 사람들의 수가 준다            for (int w : followers[u]) {                if (day[w] != 0) continue; // 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다                if (--watching[w] < need[w]) {                    day[w] = d + 1;     // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다                    tomorrow.push_back(w);                }            }        }        today.swap(tomorrow);    }    std::cout << n - left << '\n';    for (int v = 0; v < n; v++) std::cout << day[v] << (v + 1 < n ? ' ' : '\n');}
Cmain.c · 43 行
#include <stdio.h>#include <stdlib.h> int main(void) {    int n, m;    if (scanf("%d %d", &n, &m) != 2) return 0;    int *need = malloc(sizeof(int) * n);    for (int v = 0; v < n; v++) scanf("%d", &need[v]);    int *ea = malloc(sizeof(int) * (m + 1)), *eb = malloc(sizeof(int) * (m + 1));    int *watching = calloc(n, sizeof(int)); /* 지켜보는 사람 가운데 아직 방에 있는 수 */    int *start = calloc(n + 1, sizeof(int)); /* 거꾸로 된 인접 목록(CSR): b를 지켜보는 사람들이 who[start[b] .. start[b+1]) */    for (int i = 0; i < m; i++) {        scanf("%d %d", &ea[i], &eb[i]);        ea[i]--, eb[i]--;        watching[ea[i]]++;        start[eb[i] + 1]++;    }    for (int v = 0; v < n; v++) start[v + 1] += start[v];    int *fill = malloc(sizeof(int) * (n + 1)), *who = malloc(sizeof(int) * (m + 1));    for (int v = 0; v <= n; v++) fill[v] = start[v];    for (int i = 0; i < m; i++) who[fill[eb[i]]++] = ea[i];     int *day = calloc(n, sizeof(int)); /* 나간 날(0은 끝까지 남음) */    int *queue = malloc(sizeof(int) * n); /* 나간 차례: 날이 같은 사람끼리 이어져 있다 */    int head = 0, tail = 0;    for (int v = 0; v < n; v++)        if (watching[v] < need[v]) day[v] = 1, queue[tail++] = v;    while (head < tail) {        int u = queue[head++];        for (int j = start[u]; j < start[u + 1]; j++) { /* u를 지켜보던 사람들의 수가 준다 */            int w = who[j];            if (day[w] != 0) continue; /* 이미 나갔거나 나가기로 정해진 사람은 건드리지 않는다 */            if (--watching[w] < need[w]) {                day[w] = day[u] + 1; /* u의 날에 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다 */                queue[tail++] = w;            }        }    }    printf("%d\n", n - tail);    for (int v = 0; v < n; v++) printf("%d%c", day[v], v + 1 < n ? ' ' : '\n');    free(need), free(ea), free(eb), free(watching), free(start), free(fill), free(who), free(day), free(queue);    return 0;}
Craykonmain.cray · 55 行
첫줄 = コンソール.数読み();n = 첫줄[0];m = 첫줄[1];need = コンソール.数読み();followers = []; // followers[b] = b를 지켜보는 사람들(화살표를 거꾸로 저장). 번호에서 1을 빼 0번부터 둔다繰り返す (v = 0; v < n; v++){    followers.追加([]);}watching = 新リスト(n, 0); // 지켜보는 사람 가운데 아직 방에 있는 수繰り返す (i = 0; i < m; i++){    e = コンソール.数読み();    a = e[0] - 1;    b = e[1] - 1;    followers[b].追加(a);    watching[a] += 1;} day = 新リスト(n, 0); // 나간 날(0은 끝까지 남음)today = [];繰り返す (v = 0; v < n; v++){    もし (watching[v] < need[v])    {        day[v] = 1;        today.追加(v);    }}d = 1;남은수 = n;間 (today.個数 > 0){    남은수 -= today.個数;    tomorrow = [];    それぞれ (u の中 today)    {        それぞれ (w の中 followers[u])        {            もし (day[w] == 0)            {                watching[w] -= 1;                もし (watching[w] < need[w])                {                    day[w] = d + 1; // 오늘 나간 사람들이 모두 빠진 뒤에 세니, 다음 날 나간다                    tomorrow.追加(w);                }            }        }    }    today = tomorrow;    d += 1;}コンソール.書く(남은수);コンソール.書く(day);