{ }codelper
幅優先探索キューP0188

隣が多い方が取る絵の具のにじみ

R 行 C 列の紙に絵の具のしずくがいくつか落ちる。
'.' は空きマス、'#' は絵の具がしみ込まないマス、英大文字はその文字の色のしずくが落ちるマスである。
文字ごとにしずくが落ちる時刻(分)が与えられる。
しずくが落ちる前は、そのマスも空きマスとみなす。
二つのマスは上下左右で接するとき隣どうしである。
0 分から 1 分ずつ、毎分次の二つの段階を順に行う。
第 1 段階(にじむ):直前の分が終わったときに文字のある隣が一つでもある空きマスはすべて、直前の分が終わったときの状態だけを見て一斉に変わる。
その空きマスの隣でいちばん多く出てくる文字に変わり、いちばん多い文字が二つ以上あれば '*' に変わる。
'*' のマスと '#' のマスは文字とみなさず、'*' のマスは二度と変わらない。
第 2 段階(落ちる):この分に落ちるしずくは、自分のマスがまだ空きマスのときだけ、そのマスを自分の文字にする。
自分のマスがすでに別の文字か '*' になっていれば、そのしずくはなかったことになる。
0 分には直前の分がないので、落ちるだけである。
一度文字になったマスは二度と変わらない。
これ以上変わるマスも落ちるしずくもなくなったときの紙のようすを出力せよ。
落ちたしずくとにじんだマスはその文字、引き分けのマスは '*'、最後まで空きのマスは '.'、'#' はそのまま出力する。

入力
一行目に R と C、続く R 行に '.'、'#'、英大文字からなる長さ C の文字列、次の行にしずくの数 K、続く K 行に各行で文字 L と落ちる時刻 t が空白区切りで与えられる。
1 ≤ R, C ≤ 50、0 ≤ K ≤ 26、0 ≤ t ≤ 10,000。
地図に出てくる大文字はすべて異なり、K 行の文字は地図に出てくる大文字とちょうど同じである(順番はばらばら)。
出力
R 行、各行は長さ C の文字列(最後の紙のようす)
例 1 · stdin
3 5
A...B
.....
..#..
2
A 0
B 0
stdout
AA*BB
AA*BB
AA#BB
例 2 · stdin
4 4
.A#.
....
B...
...C
3
A 3
B 0
C 2
stdout
BB#B
BBBB
BBB*
BBBC
main.py
入力(stdin)

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

$ 準備完了

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

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

いくつもの場所から同時に広がるものは幅優先探索(BFS)を一層ずつ回します。
キューには直前の分に新しく塗られたマス(一層)だけを置き、その隣の空きマスをこの分の候補として集め、候補ごとに隣の文字を数えて、すべて決めてから一斉に塗ります。
直前の分に塗られたマスだけがキューにあればよい理由:もっと前に塗られたマスの隣の空きマスは、その次の分にすでに変わっているからです。
いちばん多い罠は、ふつうの BFS のようにキューから取り出すたびに隣をすぐ塗ることです — すると同じ分に着いた別の色と競うこともなく先に取り出した方が取ってしまい、引き分け('*')と多数決が消えます。
ほかの罠:引き分けをアルファベットの早い文字にあげる、'*' をキューに入れて '*' がにじむようにする、しずくをにじむより先に落とす(すると絵の具がにじんでくるちょうどその分に落ちるしずくが、覆われずに生き残ります)。
何も広がっていない間は 1 分ずつ数えず、次のしずくの時刻へ飛べばよいです(10,000 分をそのまま数えても遅くはありません)。
同じ分に一つのマスを二度候補に入れないためには、「この分に入れた」印を分の番号で残しておけば、毎分消す必要がありません。
表の扱い:Python は list(row) で行を文字のリストにし、dict で数えます。
Java は char[][] と int[26] の表、開始時刻の並べ替えは Comparator.comparingInt(Start::t)。
C# は char[][]、new int[r, c](矩形配列)、名前付きタプルのリストと OrderBy。
C++ は std::vector<std::string> と std::array<int, 26>、std::max_element・std::count でいちばん多い文字と引き分けを見つけます。
C は固定サイズの配列と qsort(比較関数が負・0・正を返します)。
JavaScript・TypeScript は Map で数えて Math.max(...votes.values())。
Craykon は地図を一文字の文字列の二次元リストで持ち、辞書() で数え、votes.キー一覧 を回っていちばん多い数とその数が何回出たかを見ます。
開始時刻の並べ替えは starts.並べ替え(時刻順) のように比較関数を渡し、行は row.つなぐ("") でつないで書きます。

Pythonmain.py · 56 行
import sys data = sys.stdin.read().split("\n")r, c = map(int, data[0].split())grid = [list(data[1 + y].strip()) for y in range(r)]k = int(data[1 + r])starts = []  # (시작 분, 글자, y, x)where = {grid[y][x]: (y, x) for y in range(r) for x in range(c) if grid[y][x].isupper()}for line in data[2 + r:2 + r + k]:    letter, t = line.split()    starts.append((int(t), letter, *where[letter]))    grid[where[letter][0]][where[letter][1]] = "."  # 시작하기 전에는 빈칸이다starts.sort() STEPS = ((-1, 0), (1, 0), (0, -1), (0, 1))frontier = []  # 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)nxt = 0        # 아직 시작하지 않은 첫 출발점minute = 0while frontier or nxt < len(starts):    if not frontier:        minute = max(minute, starts[nxt][0])  # 퍼질 것이 없으면 다음 출발 시각으로 건너뛴다    # 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다    candidates = []    seen = set()    for y, x in frontier:        for dy, dx in STEPS:            ny, nx = y + dy, x + dx            if 0 <= ny < r and 0 <= nx < c and grid[ny][nx] == "." and (ny, nx) not in seen:                seen.add((ny, nx))                candidates.append((ny, nx))    decided = []    for y, x in candidates:        votes = {}        for dy, dx in STEPS:            ny, nx = y + dy, x + dx            if 0 <= ny < r and 0 <= nx < c and grid[ny][nx].isupper():                votes[grid[ny][nx]] = votes.get(grid[ny][nx], 0) + 1        best = max(votes.values())        winners = [ch for ch, v in votes.items() if v == best]        decided.append((y, x, winners[0] if len(winners) == 1 else "*"))    new_frontier = []    for y, x, ch in decided:        grid[y][x] = ch        if ch != "*":            new_frontier.append((y, x))  # '*'는 퍼지지 않는다    # 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다    while nxt < len(starts) and starts[nxt][0] == minute:        _, letter, y, x = starts[nxt]        if grid[y][x] == ".":            grid[y][x] = letter            new_frontier.append((y, x))        nxt += 1    frontier = new_frontier    minute += 1 print("\n".join("".join(row) for row in grid))
JavaScriptmain.js · 67 行
const data = require("fs").readFileSync(0, "utf8").split("\n");const [r, c] = data[0].trim().split(/\s+/).map(Number);const grid = [];const where = new Map(); // 글자 → 칸 번호(y * c + x)for (let y = 0; y < r; y++) {  const row = data[1 + y].trim().split("");  row.forEach((ch, x) => {    if (ch >= "A" && ch <= "Z") {      where.set(ch, y * c + x);      row[x] = "."; // 시작하기 전에는 빈칸이다    }  });  grid.push(row);}const k = Number(data[1 + r]);const starts = []; // { t, letter, cell }for (let i = 0; i < k; i++) {  const [letter, t] = data[2 + r + i].trim().split(/\s+/);  starts.push({ t: Number(t), letter, cell: where.get(letter) });}starts.sort((a, b) => a.t - b.t); const isLetter = (ch) => ch >= "A" && ch <= "Z";const STEPS = [[-1, 0], [1, 0], [0, -1], [0, 1]];const mark = new Array(r * c).fill(-1); // 이번 분에 이미 후보로 넣은 칸let frontier = []; // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)let next = 0;let minute = 0;while (frontier.length > 0 || next < starts.length) {  if (frontier.length === 0) minute = Math.max(minute, starts[next].t); // 퍼질 것이 없으면 건너뛴다  // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다  const decided = [];  for (const cell of frontier) {    const y = Math.floor(cell / c), x = cell % c;    for (const [dy, dx] of STEPS) {      const ny = y + dy, nx = x + dx;      if (ny < 0 || ny >= r || nx < 0 || nx >= c || grid[ny][nx] !== "." || mark[ny * c + nx] === minute) continue;      mark[ny * c + nx] = minute;      const votes = new Map();      for (const [ey, ex] of STEPS) {        const vy = ny + ey, vx = nx + ex;        if (vy >= 0 && vy < r && vx >= 0 && vx < c && isLetter(grid[vy][vx]))          votes.set(grid[vy][vx], (votes.get(grid[vy][vx]) ?? 0) + 1);      }      const best = Math.max(...votes.values());      const winners = [...votes].filter(([, v]) => v === best);      decided.push([ny, nx, winners.length === 1 ? winners[0][0] : "*"]);    }  }  const newFrontier = [];  for (const [y, x, ch] of decided) {    grid[y][x] = ch;    if (ch !== "*") newFrontier.push(y * c + x); // '*'는 퍼지지 않는다  }  // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다  while (next < starts.length && starts[next].t === minute) {    const { letter, cell } = starts[next++];    const y = Math.floor(cell / c), x = cell % c;    if (grid[y][x] === ".") {      grid[y][x] = letter;      newFrontier.push(cell);    }  }  frontier = newFrontier;  minute++;}console.log(grid.map((row) => row.join("")).join("\n"));
TypeScriptmain.ts · 74 行
import { readFileSync } from "node:fs"; const data: string[] = readFileSync(0, "utf8").split("\n");const [r, c] = data[0].trim().split(/\s+/).map(Number);const grid: string[][] = [];const where = new Map<string, number>(); // 글자 → 칸 번호(y * c + x)for (let y = 0; y < r; y++) {  const row = data[1 + y].trim().split("");  row.forEach((ch, x) => {    if (ch >= "A" && ch <= "Z") {      where.set(ch, y * c + x);      row[x] = "."; // 시작하기 전에는 빈칸이다    }  });  grid.push(row);}const k = Number(data[1 + r]);interface Start {  t: number;  letter: string;  cell: number;}const starts: Start[] = [];for (let i = 0; i < k; i++) {  const [letter, t] = data[2 + r + i].trim().split(/\s+/);  starts.push({ t: Number(t), letter, cell: where.get(letter)! });}starts.sort((a, b) => a.t - b.t); const isLetter = (ch: string): boolean => ch >= "A" && ch <= "Z";const STEPS: [number, number][] = [[-1, 0], [1, 0], [0, -1], [0, 1]];const mark: number[] = new Array<number>(r * c).fill(-1); // 이번 분에 이미 후보로 넣은 칸let frontier: number[] = []; // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)let next = 0;let minute = 0;while (frontier.length > 0 || next < starts.length) {  if (frontier.length === 0) minute = Math.max(minute, starts[next].t); // 퍼질 것이 없으면 건너뛴다  // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다  const decided: [number, number, string][] = [];  for (const cell of frontier) {    const y = Math.floor(cell / c), x = cell % c;    for (const [dy, dx] of STEPS) {      const ny = y + dy, nx = x + dx;      if (ny < 0 || ny >= r || nx < 0 || nx >= c || grid[ny][nx] !== "." || mark[ny * c + nx] === minute) continue;      mark[ny * c + nx] = minute;      const votes = new Map<string, number>();      for (const [ey, ex] of STEPS) {        const vy = ny + ey, vx = nx + ex;        if (vy >= 0 && vy < r && vx >= 0 && vx < c && isLetter(grid[vy][vx]))          votes.set(grid[vy][vx], (votes.get(grid[vy][vx]) ?? 0) + 1);      }      const best = Math.max(...votes.values());      const winners = [...votes].filter(([, v]) => v === best);      decided.push([ny, nx, winners.length === 1 ? winners[0][0] : "*"]);    }  }  const newFrontier: number[] = [];  for (const [y, x, ch] of decided) {    grid[y][x] = ch;    if (ch !== "*") newFrontier.push(y * c + x); // '*'는 퍼지지 않는다  }  // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다  while (next < starts.length && starts[next].t === minute) {    const { letter, cell } = starts[next++];    const y = Math.floor(cell / c), x = cell % c;    if (grid[y][x] === ".") {      grid[y][x] = letter;      newFrontier.push(cell);    }  }  frontier = newFrontier;  minute++;}console.log(grid.map((row) => row.join("")).join("\n"));
JavaMain.java · 84 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Arrays;import java.util.Comparator;import java.util.List; public class Main {    record Start(int t, char letter, int y, int x) {}     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split("\\s+");        int r = Integer.parseInt(first[0]), c = Integer.parseInt(first[1]);        char[][] grid = new char[r][];        int[][] where = new int[26][];        for (int y = 0; y < r; y++) {            grid[y] = in.readLine().trim().toCharArray();            for (int x = 0; x < c; x++)                if (Character.isUpperCase(grid[y][x])) {                    where[grid[y][x] - 'A'] = new int[] {y, x};                    grid[y][x] = '.'; // 시작하기 전에는 빈칸이다                }        }        int k = Integer.parseInt(in.readLine().trim());        List<Start> starts = new ArrayList<>();        for (int i = 0; i < k; i++) {            String[] p = in.readLine().trim().split("\\s+");            char letter = p[0].charAt(0);            int[] pos = where[letter - 'A'];            starts.add(new Start(Integer.parseInt(p[1]), letter, pos[0], pos[1]));        }        starts.sort(Comparator.comparingInt(Start::t));         int[] dy = {-1, 1, 0, 0}, dx = {0, 0, -1, 1};        int[][] mark = new int[r][c]; // 이번 분에 이미 후보로 넣은 칸        for (int[] row : mark) Arrays.fill(row, -1);        List<int[]> frontier = new ArrayList<>(); // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)        int next = 0, minute = 0;        while (!frontier.isEmpty() || next < k) {            if (frontier.isEmpty()) minute = Math.max(minute, starts.get(next).t()); // 퍼질 것이 없으면 건너뛴다            // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다            List<int[]> decided = new ArrayList<>(); // {y, x, 글자}            for (int[] cell : frontier)                for (int d = 0; d < 4; d++) {                    int ny = cell[0] + dy[d], nx = cell[1] + dx[d];                    if (ny < 0 || ny >= r || nx < 0 || nx >= c || grid[ny][nx] != '.' || mark[ny][nx] == minute) continue;                    mark[ny][nx] = minute;                    int[] votes = new int[26];                    for (int e = 0; e < 4; e++) {                        int vy = ny + dy[e], vx = nx + dx[e];                        if (vy >= 0 && vy < r && vx >= 0 && vx < c && Character.isUpperCase(grid[vy][vx]))                            votes[grid[vy][vx] - 'A']++;                    }                    int best = 0, winners = 0;                    char win = '*';                    for (int l = 0; l < 26; l++) {                        if (votes[l] > best) { best = votes[l]; winners = 1; win = (char) ('A' + l); }                        else if (votes[l] == best && best > 0) winners++;                    }                    decided.add(new int[] {ny, nx, winners == 1 ? win : '*'});                }            List<int[]> fresh = new ArrayList<>();            for (int[] dcd : decided) {                grid[dcd[0]][dcd[1]] = (char) dcd[2];                if (dcd[2] != '*') fresh.add(new int[] {dcd[0], dcd[1]}); // '*'는 퍼지지 않는다            }            // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다            for (; next < k && starts.get(next).t() == minute; next++) {                Start s = starts.get(next);                if (grid[s.y()][s.x()] == '.') {                    grid[s.y()][s.x()] = s.letter();                    fresh.add(new int[] {s.y(), s.x()});                }            }            frontier = fresh;            minute++;        }        StringBuilder out = new StringBuilder();        for (char[] row : grid) out.append(row).append('\n');        System.out.print(out);    }}
C#Main.cs · 86 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    static void Main()    {        int[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        int r = first[0], c = first[1];        var grid = new char[r][];        var where = new Dictionary<char, (int y, int x)>();        for (int y = 0; y < r; y++)        {            grid[y] = Console.ReadLine().Trim().ToCharArray();            for (int x = 0; x < c; x++)                if (char.IsUpper(grid[y][x]))                {                    where[grid[y][x]] = (y, x);                    grid[y][x] = '.'; // 시작하기 전에는 빈칸이다                }        }        int k = int.Parse(Console.ReadLine().Trim());        var starts = new List<(int t, char letter, int y, int x)>();        for (int i = 0; i < k; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            char letter = p[0][0];            var (sy, sx) = where[letter];            starts.Add((int.Parse(p[1]), letter, sy, sx));        }        starts = starts.OrderBy(s => s.t).ToList();         int[] dy = { -1, 1, 0, 0 }, dx = { 0, 0, -1, 1 };        bool Inside(int y, int x) => y >= 0 && y < r && x >= 0 && x < c;        var mark = new int[r, c]; // 이번 분에 이미 후보로 넣은 칸        for (int y = 0; y < r; y++)            for (int x = 0; x < c; x++) mark[y, x] = -1;        var frontier = new List<(int y, int x)>(); // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)        int next = 0, minute = 0;        while (frontier.Count > 0 || next < k)        {            if (frontier.Count == 0) minute = Math.Max(minute, starts[next].t); // 퍼질 것이 없으면 건너뛴다            // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다            var decided = new List<(int y, int x, char ch)>();            foreach (var (y, x) in frontier)                for (int d = 0; d < 4; d++)                {                    int ny = y + dy[d], nx = x + dx[d];                    if (!Inside(ny, nx) || grid[ny][nx] != '.' || mark[ny, nx] == minute) continue;                    mark[ny, nx] = minute;                    var votes = new int[26];                    for (int e = 0; e < 4; e++)                    {                        int vy = ny + dy[e], vx = nx + dx[e];                        if (Inside(vy, vx) && char.IsUpper(grid[vy][vx])) votes[grid[vy][vx] - 'A']++;                    }                    int best = votes.Max();                    bool single = votes.Count(v => v == best) == 1;                    decided.Add((ny, nx, single ? (char)('A' + Array.IndexOf(votes, best)) : '*'));                }            var fresh = new List<(int y, int x)>();            foreach (var (y, x, ch) in decided)            {                grid[y][x] = ch;                if (ch != '*') fresh.Add((y, x)); // '*'는 퍼지지 않는다            }            // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다            for (; next < k && starts[next].t == minute; next++)            {                var s = starts[next];                if (grid[s.y][s.x] == '.')                {                    grid[s.y][s.x] = s.letter;                    fresh.Add((s.y, s.x));                }            }            frontier = fresh;            minute++;        }        var out_ = new StringBuilder();        foreach (var row in grid) out_.Append(row).Append('\n');        Console.Write(out_);    }}
C++main.cpp · 78 行
#include <algorithm>#include <cctype>#include <array>#include <iostream>#include <string>#include <tuple>#include <vector> struct Start {    int t;    char letter;    int y, x;}; int main() {    int r, c, k;    std::cin >> r >> c;    std::vector<std::string> grid(r);    std::array<std::pair<int, int>, 26> where{};    for (int y = 0; y < r; y++) {        std::cin >> grid[y];        for (int x = 0; x < c; x++)            if (std::isupper(static_cast<unsigned char>(grid[y][x]))) {                where[grid[y][x] - 'A'] = {y, x};                grid[y][x] = '.';  // 시작하기 전에는 빈칸이다            }    }    std::cin >> k;    std::vector<Start> starts(k);    for (auto& s : starts) {        std::cin >> s.letter >> s.t;        std::tie(s.y, s.x) = where[s.letter - 'A'];    }    std::sort(starts.begin(), starts.end(), [](const Start& a, const Start& b) { return a.t < b.t; });     const int DY[4] = {-1, 1, 0, 0}, DX[4] = {0, 0, -1, 1};    auto inside = [&](int y, int x) { return y >= 0 && y < r && x >= 0 && x < c; };    std::vector<std::vector<int>> mark(r, std::vector<int>(c, -1));  // 이번 분에 이미 후보로 넣은 칸    std::vector<std::pair<int, int>> frontier;  // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)    std::size_t next = 0;    int minute = 0;    while (!frontier.empty() || next < starts.size()) {        if (frontier.empty()) minute = std::max(minute, starts[next].t);  // 퍼질 것이 없으면 건너뛴다        // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다        std::vector<std::tuple<int, int, char>> decided;        for (auto [y, x] : frontier)            for (int d = 0; d < 4; d++) {                int ny = y + DY[d], nx = x + DX[d];                if (!inside(ny, nx) || grid[ny][nx] != '.' || mark[ny][nx] == minute) continue;                mark[ny][nx] = minute;                std::array<int, 26> votes{};                for (int e = 0; e < 4; e++) {                    int vy = ny + DY[e], vx = nx + DX[e];                    if (inside(vy, vx) && std::isupper(static_cast<unsigned char>(grid[vy][vx]))) votes[grid[vy][vx] - 'A']++;                }                int best = *std::max_element(votes.begin(), votes.end());                int winners = static_cast<int>(std::count(votes.begin(), votes.end(), best));                char win = static_cast<char>('A' + (std::max_element(votes.begin(), votes.end()) - votes.begin()));                decided.emplace_back(ny, nx, winners == 1 ? win : '*');            }        std::vector<std::pair<int, int>> fresh;        for (auto [y, x, ch] : decided) {            grid[y][x] = ch;            if (ch != '*') fresh.emplace_back(y, x);  // '*'는 퍼지지 않는다        }        // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다        for (; next < starts.size() && starts[next].t == minute; next++) {            const Start& s = starts[next];            if (grid[s.y][s.x] == '.') {                grid[s.y][s.x] = s.letter;                fresh.emplace_back(s.y, s.x);            }        }        frontier = std::move(fresh);        minute++;    }    for (const auto& row : grid) std::cout << row << "\n";}
Cmain.c · 97 行
#include <stdio.h>#include <stdlib.h> #define MAXN 50 static char grid[MAXN][MAXN + 2];static int mark[MAXN * MAXN];       /* 이번 분에 이미 후보로 넣은 칸 */static int frontier[MAXN * MAXN], nf; /* 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹) */static int fresh[MAXN * MAXN], nfresh;static int decY[MAXN * MAXN], decX[MAXN * MAXN];static char decCh[MAXN * MAXN]; typedef struct {    int t;    char letter;    int cell;} Start; static int byTime(const void *a, const void *b) {    return ((const Start *)a)->t - ((const Start *)b)->t;} int main(void) {    int r, c, k;    int where[26];    Start starts[26];    scanf("%d %d", &r, &c);    for (int y = 0; y < r; y++) {        scanf("%51s", grid[y]);        for (int x = 0; x < c; x++)            if (grid[y][x] >= 'A' && grid[y][x] <= 'Z') {                where[grid[y][x] - 'A'] = y * c + x;                grid[y][x] = '.'; /* 시작하기 전에는 빈칸이다 */            }    }    scanf("%d", &k);    for (int i = 0; i < k; i++) {        char letter[4];        scanf("%3s %d", letter, &starts[i].t);        starts[i].letter = letter[0];        starts[i].cell = where[letter[0] - 'A'];    }    qsort(starts, k, sizeof starts[0], byTime);    for (int i = 0; i < r * c; i++) mark[i] = -1;     const int DY[4] = {-1, 1, 0, 0}, DX[4] = {0, 0, -1, 1};    int next = 0, minute = 0;    nf = 0;    while (nf > 0 || next < k) {        if (nf == 0 && starts[next].t > minute) minute = starts[next].t; /* 퍼질 것이 없으면 건너뛴다 */        /* 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다 */        int nd = 0;        for (int i = 0; i < nf; i++) {            int y = frontier[i] / c, x = frontier[i] % c;            for (int d = 0; d < 4; d++) {                int ny = y + DY[d], nx = x + DX[d];                if (ny < 0 || ny >= r || nx < 0 || nx >= c || grid[ny][nx] != '.' || mark[ny * c + nx] == minute) continue;                mark[ny * c + nx] = minute;                int votes[26] = {0};                for (int e = 0; e < 4; e++) {                    int vy = ny + DY[e], vx = nx + DX[e];                    if (vy >= 0 && vy < r && vx >= 0 && vx < c && grid[vy][vx] >= 'A' && grid[vy][vx] <= 'Z')                        votes[grid[vy][vx] - 'A']++;                }                int best = 0, winners = 0;                char win = '*';                for (int l = 0; l < 26; l++) {                    if (votes[l] > best) { best = votes[l]; winners = 1; win = (char)('A' + l); }                    else if (votes[l] == best && best > 0) winners++;                }                decY[nd] = ny;                decX[nd] = nx;                decCh[nd] = winners == 1 ? win : '*';                nd++;            }        }        nfresh = 0;        for (int i = 0; i < nd; i++) {            grid[decY[i]][decX[i]] = decCh[i];            if (decCh[i] != '*') fresh[nfresh++] = decY[i] * c + decX[i]; /* '*'는 퍼지지 않는다 */        }        /* 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다 */        while (next < k && starts[next].t == minute) {            int y = starts[next].cell / c, x = starts[next].cell % c;            if (grid[y][x] == '.') {                grid[y][x] = starts[next].letter;                fresh[nfresh++] = starts[next].cell;            }            next++;        }        for (int i = 0; i < nfresh; i++) frontier[i] = fresh[i];        nf = nfresh;        minute++;    }    for (int y = 0; y < r; y++) puts(grid[y]);    return 0;}
Craykonmain.cray · 145 行
첫줄 = コンソール.数読み();r = 첫줄[0];c = 첫줄[1]; 글자인가(ch){    返す ch >= "A" && ch <= "Z";} // 칸은 수 하나(y * c + x)로 다룬다. 지도는 한 글자짜리 문자열의 2차원 목록grid = [];where = 辞書(); // 글자 → 칸 번호繰り返す (y = 0; y < r; y++){    줄 = コンソール.行読み().空白除去();    row = [];    繰り返す (x = 0; x < c; x++)    {        ch = 줄[x];        もし (글자인가(ch))        {            where[ch] = y * c + x;            ch = "."; // 시작하기 전에는 빈칸이다        }        row.追加(ch);    }    grid.追加(row);}k = コンソール.行読み().空白除去().数に();starts = []; // [시작 분, 글자, 칸 번호]繰り返す (i = 0; i < k; i++){    p = コンソール.行読み().空白除去().分ける(" ");    starts.追加([p[1].数に(), p[0], where[p[0]]]);} 시각순(앞, 뒤){    返す 앞[0] - 뒤[0];} starts.並べ替え(시각순); DY = [-1, 1, 0, 0];DX = [0, 0, -1, 1]; 안인가(y, x){    返す y >= 0 && y < r && x >= 0 && x < c;} mark = 新リスト(r * c, -1); // 이번 분에 이미 후보로 넣은 칸frontier = []; // 직전 분에 새로 글자를 얻은 칸들(너비 우선 탐색의 한 겹)next = 0;minute = 0;間 (frontier.個数 > 0 || next < k){    もし (frontier.個数 == 0)    {        minute = 数学.大きい方(minute, starts[next][0]); // 퍼질 것이 없으면 건너뛴다    }    // 1) 퍼지기: 직전 분 상태로 모든 빈칸을 한꺼번에 정한다    decided = []; // [칸 번호, 글자]    それぞれ (cell の中 frontier)    {        y = 数学.商(cell, c);        x = cell % c;        繰り返す (d = 0; d < 4; d++)        {            ny = y + DY[d];            nx = x + DX[d];            もし (안인가(ny, nx) && grid[ny][nx] == "." && mark[ny * c + nx] != minute)            {                mark[ny * c + nx] = minute;                votes = 辞書();                繰り返す (e = 0; e < 4; e++)                {                    vy = ny + DY[e];                    vx = nx + DX[e];                    もし (안인가(vy, vx) && 글자인가(grid[vy][vx]))                    {                        ch = grid[vy][vx];                        もし (votes.含む(ch))                        {                            votes[ch] += 1;                        }                        でなければ                        {                            votes[ch] = 1;                        }                    }                }                best = 0;                winners = 0;                win = "*";                それぞれ (ch の中 votes.キー一覧)                {                    もし (votes[ch] > best)                    {                        best = votes[ch];                        winners = 1;                        win = ch;                    }                    でなければ もし (votes[ch] == best)                    {                        winners += 1;                    }                }                もし (winners > 1)                {                    win = "*";                }                decided.追加([ny * c + nx, win]);            }        }    }    fresh = [];    それぞれ (dcd の中 decided)    {        grid[数学.商(dcd[0], c)][dcd[0] % c] = dcd[1];        もし (dcd[1] != "*")        {            fresh.追加(dcd[0]); // '*'는 퍼지지 않는다        }    }    // 2) 시작: 이 분에 시작하는 출발점은 칸이 아직 빈칸일 때만 시작한다    間 (next < k && starts[next][0] == minute)    {        cell = starts[next][2];        y = 数学.商(cell, c);        x = cell % c;        もし (grid[y][x] == ".")        {            grid[y][x] = starts[next][1];            fresh.追加(cell);        }        next += 1;    }    frontier = fresh;    minute += 1;}それぞれ (row の中 grid){    コンソール.書く(row.つなぐ(""));}