{ }codelper
幅優先探索P0378

ストーブの暖かさが広がる教室

冬の朝、H 行 W 列の教室にストーブが 1 つある。
地図で '.' は床のマス、'#' は本棚が置かれたマス、'S' はストーブが置かれたマスである。
0 分にはストーブのマスだけが暖かい。
1 分たつごとに、暖かいマスと上・下・左・右で接している床のマスが新しく暖かくなる。
本棚のマスは暖かくならず、暖かさは本棚を通り抜けられない。
暖かさは教室の外に出ない。
いちばん上の行の上、いちばん下の行の下、いちばん左のマスの左、いちばん右のマスの右にはマスがない。
一度暖かくなったマスはずっと暖かい。
新しく暖かくなったマスがある最後の時刻を T 分とする。
ストーブのマス以外に暖かくなるマスがなければ T は 0 である。
1 行目に T を出力せよ。
2 行目に、0 分、1 分、…、T 分に新しく暖かくなったマスの数を順に、空白 1 つで区切って出力せよ。
0 分の値はストーブのマス 1 つなので、いつも 1 である。
3 行目に、最後まで暖かくならない床のマスの数を出力せよ。

入力
1 行目に H と W が空白 1 つで区切られて与えられる。
続く H 行に教室の地図が 1 行ずつ与えられ、各行は '.'、'#'、'S' だけからなる長さ W の文字列である。
1 ≤ H ≤ 20、1 ≤ W ≤ 20。
'S' は地図全体にちょうど 1 つある
出力
3 行。
1 行目に T、2 行目に 0 分から T 分までに新しく暖かくなったマスの数 T + 1 個、3 行目に最後まで暖かくならない床のマスの数
例 1 · stdin
4 5
S..#.
.#.##
....#
...#.
stdout
5
1 2 2 3 2 2
2
例 2 · stdin
1 4
.S#.
stdout
1
1 1
1
main.py
入力(stdin)

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

$ 準備完了

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

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

ストーブのマスをキューに入れ、前から 1 つずつ取り出して 4 つの隣を調べ、初めて届いたマスに「取り出したマスの時刻 + 1」を書いてキューの後ろに入れます。
4 方向は dr = [-1, 1, 0, 0]、dc = [0, 0, -1, 1] の 2 つの配列にしておくと、ループ 1 つで回れます。
キューは Python deque(popleft)、Java ArrayDeque(pollFirst)、C# Queue<T>(Dequeue)、C++ std::queue(front で見て pop で外す)を使い、JS・TS・C・Craykon は配列の後ろに入れながら「先頭の位置」の番号 1 つだけを後ろへ進めます。
言語ごとにいちばん大きく分かれるのは、教室の外のマスを読んだときです。
落とし穴 1:0 <= nr を抜いて nr < H だけを確かめると、Python は止まりません。
grid[-1] は最後の行なので、暖かさが壁を突き抜けて反対側へ出ます。
例 2 ではストーブの左のマスのさらに左がいちばん右のマスとして読まれ、2 と 1 1 1、0 になり、例 1 は 1 4 4 3 1 1 と 0 になります。
JS・TS は grid[-1] が undefined なので、その次の [nc] で TypeError で止まり、Craykon も「リストには 0〜3 番しかありません」というエラーで止まります。
Java と C# も例外で止まりますが、C と C++ の [] は範囲を確かめません。
配列の外を読むと何が起こるかは決まっていません(未定義動作)。
だから 8 つの解答はどれも、地図を読む前に 4 つの範囲を先に確かめます。
落とし穴 2:「暖かくなった」という印をキューに入れるときではなく取り出すときに付けると、2 つの隣から同じ分に暖かさを受け取るマスがキューに 2 回入り、2 回数えられます。
例 1 の 2 行目が 1 2 2 3 4 3 になります。
落とし穴 3:いちばん後ろから取り出すと(JS pop())、キューではなくスタックになり、近いマスより先に遠いマスへ潜っていきます。
例 1 が 7 と 1 2 1 1 1 3 2 1 になります。
JS の shift() も先頭から取り出してくれます。
マスが多くても 400 個のこの問題では差が出ませんが、採点サーバーの Node 22 で測ると、200,000 個を入れて shift() で全部取り出すのに約 3 秒かかり、位置の番号を進める方法は 0.01 秒もかかりませんでした。

Pythonmain.py · 33 行
import sysfrom collections import deque lines = sys.stdin.read().split("\n")H, W = map(int, lines[0].split())grid = lines[1:1 + H] minute = [[-1] * W for _ in range(H)]    # minute[r][c] = 따뜻해진 시각, 아직이면 -1queue = deque()for r in range(H):    for c in range(W):        if grid[r][c] == "S":            minute[r][c] = 0            queue.append((r, c)) counts = [1]                             # counts[t] = t분에 새로 따뜻해진 칸의 수while queue:    r, c = queue.popleft()               # 먼저 따뜻해진 칸부터 꺼낸다    for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):        nr, nc = r + dr, c + dc        # 교실 밖인지 먼저 묻는다. Python의 grid[-1]은 오류가 아니라 맨 끝 줄이다        if 0 <= nr < H and 0 <= nc < W and grid[nr][nc] != "#" and minute[nr][nc] == -1:            t = minute[r][c] + 1            minute[nr][nc] = t           # 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다            if t == len(counts):                counts.append(0)            counts[t] += 1            queue.append((nr, nc)) cold = sum(1 for r in range(H) for c in range(W) if grid[r][c] == "." and minute[r][c] == -1)print(len(counts) - 1)print(" ".join(map(str, counts)))print(cold)
JavaScriptmain.js · 46 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [H, W] = lines[0].trim().split(" ").map(Number);const grid = lines.slice(1, 1 + H); // minute[r][c] = 따뜻해진 시각, 아직이면 -1const minute = Array.from({ length: H }, () => new Array(W).fill(-1));// JS에는 큐가 따로 없다. 배열 뒤에 넣고, 맨 앞 자리(head)만 뒤로 옮긴다const queue = [];let head = 0;for (let r = 0; r < H; r++) {  for (let c = 0; c < W; c++) {    if (grid[r][c] === "S") {      minute[r][c] = 0;      queue.push([r, c]);    }  }} const counts = [1]; // counts[t] = t분에 새로 따뜻해진 칸의 수const dr = [-1, 1, 0, 0];const dc = [0, 0, -1, 1];while (head < queue.length) {  const [r, c] = queue[head++]; // 먼저 따뜻해진 칸부터 꺼낸다  for (let d = 0; d < 4; d++) {    const nr = r + dr[d];    const nc = c + dc[d];    // 교실 밖인지 먼저 묻는다. grid[-1]은 undefined라서 그 뒤의 [nc]에서 오류가 난다    if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;    if (grid[nr][nc] === "#" || minute[nr][nc] !== -1) continue;    const t = minute[r][c] + 1;    minute[nr][nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다    if (t === counts.length) counts.push(0);    counts[t]++;    queue.push([nr, nc]);  }} let cold = 0;for (let r = 0; r < H; r++) {  for (let c = 0; c < W; c++) {    if (grid[r][c] === "." && minute[r][c] === -1) cold++;  }}console.log(counts.length - 1);console.log(counts.join(" "));console.log(cold);
TypeScriptmain.ts · 48 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const [H, W]: number[] = lines[0].trim().split(" ").map(Number);const grid: string[] = lines.slice(1, 1 + H); // minute[r][c] = 따뜻해진 시각, 아직이면 -1const minute: number[][] = Array.from({ length: H }, (): number[] => new Array<number>(W).fill(-1));// TS에도 큐가 따로 없다. 배열 뒤에 넣고, 맨 앞 자리(head)만 뒤로 옮긴다const queue: [number, number][] = [];let head = 0;for (let r = 0; r < H; r++) {  for (let c = 0; c < W; c++) {    if (grid[r][c] === "S") {      minute[r][c] = 0;      queue.push([r, c]);    }  }} const counts: number[] = [1]; // counts[t] = t분에 새로 따뜻해진 칸의 수const dr: number[] = [-1, 1, 0, 0];const dc: number[] = [0, 0, -1, 1];while (head < queue.length) {  const [r, c] = queue[head++]; // 먼저 따뜻해진 칸부터 꺼낸다  for (let d = 0; d < 4; d++) {    const nr = r + dr[d];    const nc = c + dc[d];    // 교실 밖인지 먼저 묻는다. grid[-1]은 undefined라서 그 뒤의 [nc]에서 오류가 난다    if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;    if (grid[nr][nc] === "#" || minute[nr][nc] !== -1) continue;    const t = minute[r][c] + 1;    minute[nr][nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다    if (t === counts.length) counts.push(0);    counts[t]++;    queue.push([nr, nc]);  }} let cold = 0;for (let r = 0; r < H; r++) {  for (let c = 0; c < W; c++) {    if (grid[r][c] === "." && minute[r][c] === -1) cold++;  }}console.log(counts.length - 1);console.log(counts.join(" "));console.log(cold);
JavaMain.java · 64 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.Arrays; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split(" ");        int H = Integer.parseInt(first[0]);        int W = Integer.parseInt(first[1]);        char[][] grid = new char[H][];        int[][] minute = new int[H][W]; // minute[r][c] = 따뜻해진 시각, 아직이면 -1        ArrayDeque<int[]> queue = new ArrayDeque<>();        for (int r = 0; r < H; r++) {            grid[r] = in.readLine().trim().toCharArray();            Arrays.fill(minute[r], -1);            for (int c = 0; c < W; c++) {                if (grid[r][c] == 'S') {                    minute[r][c] = 0;                    queue.addLast(new int[] {r, c});                }            }        }         int[] counts = new int[H * W]; // counts[t] = t분에 새로 따뜻해진 칸의 수        counts[0] = 1;        int last = 0;                  // 마지막으로 새 칸이 따뜻해진 시각        int[] dr = {-1, 1, 0, 0};        int[] dc = {0, 0, -1, 1};        while (!queue.isEmpty()) {            int[] cell = queue.pollFirst(); // 먼저 따뜻해진 칸부터 꺼낸다            int r = cell[0], c = cell[1];            for (int d = 0; d < 4; d++) {                int nr = r + dr[d];                int nc = c + dc[d];                // 교실 밖인지 먼저 묻는다. 묻지 않으면 ArrayIndexOutOfBoundsException으로 멈춘다                if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;                if (grid[nr][nc] == '#' || minute[nr][nc] != -1) continue;                int t = minute[r][c] + 1;                minute[nr][nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다                counts[t]++;                last = t;                queue.addLast(new int[] {nr, nc});            }        }         int cold = 0;        for (int r = 0; r < H; r++) {            for (int c = 0; c < W; c++) {                if (grid[r][c] == '.' && minute[r][c] == -1) cold++;            }        }        StringBuilder out = new StringBuilder();        out.append(last).append('\n');        for (int t = 0; t <= last; t++) {            if (t > 0) out.append(' ');            out.append(counts[t]);        }        out.append('\n').append(cold);        System.out.println(out);    }}
C#Main.cs · 61 行
using System;using System.Collections.Generic; class Program{    static void Main()    {        string[] first = Console.ReadLine().Trim().Split(' ');        int H = int.Parse(first[0]);        int W = int.Parse(first[1]);        string[] grid = new string[H];        int[,] minute = new int[H, W]; // minute[r, c] = 따뜻해진 시각, 아직이면 -1        var queue = new Queue<(int r, int c)>();        for (int r = 0; r < H; r++)        {            grid[r] = Console.ReadLine().Trim();            for (int c = 0; c < W; c++)            {                minute[r, c] = -1;                if (grid[r][c] == 'S')                {                    minute[r, c] = 0;                    queue.Enqueue((r, c));                }            }        }         var counts = new List<int> { 1 }; // counts[t] = t분에 새로 따뜻해진 칸의 수        int[] dr = { -1, 1, 0, 0 };        int[] dc = { 0, 0, -1, 1 };        while (queue.Count > 0)        {            var (r, c) = queue.Dequeue(); // 먼저 따뜻해진 칸부터 꺼낸다            for (int d = 0; d < 4; d++)            {                int nr = r + dr[d];                int nc = c + dc[d];                // 교실 밖인지 먼저 묻는다. 묻지 않으면 IndexOutOfRangeException으로 멈춘다                if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;                if (grid[nr][nc] == '#' || minute[nr, nc] != -1) continue;                int t = minute[r, c] + 1;                minute[nr, nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다                if (t == counts.Count) counts.Add(0);                counts[t]++;                queue.Enqueue((nr, nc));            }        }         int cold = 0;        for (int r = 0; r < H; r++)        {            for (int c = 0; c < W; c++)            {                if (grid[r][c] == '.' && minute[r, c] == -1) cold++;            }        }        Console.WriteLine(counts.Count - 1);        Console.WriteLine(string.Join(" ", counts));        Console.WriteLine(cold);    }}
C++main.cpp · 57 行
#include <iostream>#include <queue>#include <string>#include <utility>#include <vector>using namespace std; int main() {    int H, W;    cin >> H >> W;    vector<string> grid(H);    vector<vector<int>> minute(H, vector<int>(W, -1)); // 따뜻해진 시각, 아직이면 -1    queue<pair<int, int>> q;    for (int r = 0; r < H; r++) {        cin >> grid[r];        for (int c = 0; c < W; c++) {            if (grid[r][c] == 'S') {                minute[r][c] = 0;                q.push({r, c});            }        }    }     vector<int> counts = {1}; // counts[t] = t분에 새로 따뜻해진 칸의 수    const int dr[4] = {-1, 1, 0, 0};    const int dc[4] = {0, 0, -1, 1};    while (!q.empty()) {        auto [r, c] = q.front(); // 먼저 따뜻해진 칸부터 꺼낸다        q.pop();                 // pop()은 값을 돌려주지 않는다        for (int d = 0; d < 4; d++) {            int nr = r + dr[d];            int nc = c + dc[d];            // 교실 밖인지 먼저 묻는다. C++의 []는 범위를 확인하지 않고, 배열 밖을 읽으면 정의되지 않은 동작이다            if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;            if (grid[nr][nc] == '#' || minute[nr][nc] != -1) continue;            int t = minute[r][c] + 1;            minute[nr][nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다            if (t == (int)counts.size()) counts.push_back(0);            counts[t]++;            q.push({nr, nc});        }    }     int cold = 0;    for (int r = 0; r < H; r++) {        for (int c = 0; c < W; c++) {            if (grid[r][c] == '.' && minute[r][c] == -1) cold++;        }    }    cout << counts.size() - 1 << '\n';    for (size_t t = 0; t < counts.size(); t++) {        if (t > 0) cout << ' ';        cout << counts[t];    }    cout << '\n' << cold << '\n';    return 0;}
Cmain.c · 62 行
#include <stdio.h> #define MAXS 20 int main(void) {    int H, W;    static char grid[MAXS][MAXS + 1];    static int minute[MAXS][MAXS];        /* 따뜻해진 시각, 아직이면 -1 */    static int queue_r[MAXS * MAXS];      /* 배열로 만든 큐: tail 자리에 넣고 head 자리에서 꺼낸다 */    static int queue_c[MAXS * MAXS];    static int counts[MAXS * MAXS];       /* counts[t] = t분에 새로 따뜻해진 칸의 수 */    int head = 0, tail = 0, last = 0;     scanf("%d %d", &H, &W);    for (int r = 0; r < H; r++) {        scanf("%20s", grid[r]);        for (int c = 0; c < W; c++) {            minute[r][c] = -1;            if (grid[r][c] == 'S') {                minute[r][c] = 0;                queue_r[tail] = r;                queue_c[tail] = c;                tail++;            }        }    }     counts[0] = 1;    const int dr[4] = {-1, 1, 0, 0};    const int dc[4] = {0, 0, -1, 1};    while (head < tail) {        int r = queue_r[head], c = queue_c[head]; /* 먼저 따뜻해진 칸부터 꺼낸다 */        head++;        for (int d = 0; d < 4; d++) {            int nr = r + dr[d];            int nc = c + dc[d];            /* 교실 밖인지 먼저 묻는다. C는 범위를 확인하지 않고, 배열 밖을 읽으면 정의되지 않은 동작이다 */            if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;            if (grid[nr][nc] == '#' || minute[nr][nc] != -1) continue;            int t = minute[r][c] + 1;            minute[nr][nc] = t; /* 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다 */            counts[t]++;            last = t;            queue_r[tail] = nr;            queue_c[tail] = nc;            tail++;        }    }     int cold = 0;    for (int r = 0; r < H; r++) {        for (int c = 0; c < W; c++) {            if (grid[r][c] == '.' && minute[r][c] == -1) cold++;        }    }    printf("%d\n", last);    for (int t = 0; t <= last; t++) {        printf(t > 0 ? " %d" : "%d", counts[t]);    }    printf("\n%d\n", cold);    return 0;}
Craykonmain.cray · 73 行
첫줄 = コンソール.数読み();H = 첫줄[0];W = 첫줄[1];grid = [];minute = []; // minute[r][c] = 따뜻해진 시각, 아직이면 -1// 목록으로 만든 큐: 뒤에 넣고, 맨 앞 자리(head)만 뒤로 옮긴다. 줄 번호와 칸 번호를 따로 담는다queueR = [];queueC = [];head = 0;繰り返す (r = 0; r < H; r++){    줄 = コンソール.行読み().空白除去();    grid.追加(줄);    minute.追加(新リスト(W, -1));    繰り返す (c = 0; c < W; c++)    {        もし (줄[c] == "S")        {            minute[r][c] = 0;            queueR.追加(r);            queueC.追加(c);        }    }} counts = [1]; // counts[t] = t분에 새로 따뜻해진 칸의 수dr = [-1, 1, 0, 0];dc = [0, 0, -1, 1];間 (head < queueR.個数){    // 먼저 따뜻해진 칸부터 꺼낸다    r = queueR[head];    c = queueC[head];    head += 1;    繰り返す (d = 0; d < 4; d++)    {        nr = r + dr[d];        nc = c + dc[d];        // 교실 밖인지 먼저 묻는다. 묻지 않고 목록 밖을 읽으면 오류로 멈춘다        もし (nr < 0 || nr >= H || nc < 0 || nc >= W)        {            次へ;        }        もし (grid[nr][nc] == "#" || minute[nr][nc] != -1)        {            次へ;        }        t = minute[r][c] + 1;        minute[nr][nc] = t; // 큐에 넣을 때 바로 표시해야 같은 칸이 두 번 들어가지 않는다        もし (t == counts.個数)        {            counts.追加(0);        }        counts[t] += 1;        queueR.追加(nr);        queueC.追加(nc);    }} cold = 0;繰り返す (r = 0; r < H; r++){    繰り返す (c = 0; c < W; c++)    {        もし (grid[r][c] == "." && minute[r][c] == -1)        {            cold += 1;        }    }}コンソール.書く(counts.個数 - 1);コンソール.書く(counts.つなぐ(" "));コンソール.書く(cold);