{ }codelper
判定関数2次元リストP0299

攻撃されない駒

縦 h マス、横 w マスの盤に駒が置かれている。
マスは .(空き)か、駒一つを表す文字 R、B、Q、N、K のどれかである。
すべての駒は互いに敵で、どの駒もほかの駒を攻撃できる。
R は自分と同じ横の列か同じ縦の列にある駒を攻撃する。
B は自分と同じ斜めの線上にある駒、つまり行番号の差と列番号の差の大きさが等しい駒を攻撃する。
Q は R が攻撃する駒と B が攻撃する駒をすべて攻撃する。
ただし R、B、Q は、自分と相手の駒の間のマス(二つの駒があるマスを除く、二つを結ぶまっすぐな列または斜めの線上のマス)にほかの駒が一つでもあると、その相手の駒を攻撃できない。
つまり、ほかの駒を飛び越えて攻撃することはできない。
N は横に 1 マス・縦に 2 マス、または横に 2 マス・縦に 1 マス離れた駒を攻撃し、間にある駒には邪魔されない。
K は縦、横、斜めにすぐ隣り合う八つのマスの駒を攻撃する。
盤が t 個与えられるとき、盤ごとに、ほかのどの駒からも攻撃されない駒の個数を求めよ。
一行目に t が与えられる。
盤ごとに、最初の行に h と w が空白一つで区切って与えられ、続く h 行に長さ w の文字列が与えられる。
盤ごとに答えを一行に一つずつ出力する。
駒のない盤の答えは 0 である。

入力
1 ≤ t ≤ 30。
1 ≤ h, w ≤ 12。
一つの盤の駒は 0 個以上 40 個以下。
盤の文字列には .、R、B、Q、N、K だけがあり、空白はない
出力
盤ごとに、攻撃されない駒の個数を一行に一つずつ
例 1 · stdin
2
3 4
R..N
.B..
K..Q
1 5
R.N.R
stdout
1
2
main.py
入力(stdin)

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

$ 準備完了

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

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

「~か?」という問いを真偽を返す小さな関数に分けると(同じ列か、斜めか、間が空いているか、攻撃するか、安全か)、複雑な条件が名前のついた問いの組み合わせとして読めます。
戻り値の型は Python bool(True/False)、JS・TS boolean、Java boolean、C#・C++ bool、C は <stdbool.h> の bool、Craykon は 真/偽 です。
「一つでもあるか」は Python any(...)、JS・TS some、Java ストリームの anyMatch/noneMatch、C# Any、C++ std::any_of/std::none_of と書き、C と Craykon はループの中で見つけたらすぐ 返す で終わります。
絶対値は Python・C abs、C++ std::abs、JS・TS・Java Math.abs、C# Math.Abs、Craykon 数学.絶対値。
符号は JS・TS Math.sign、Java Integer.signum、C# Math.Sign がありますが、Python・C・C++・Craykon には整数用の符号関数がないので自分で書きます。
落とし穴 ① ルーク・ビショップ・クイーンは間にある駒に遮られます。
向きだけ見て終わってはいけません。
② 間を調べるループで最初のマスだけ見てすぐ真を返す間違い:真はループが全部終わってから返します。
③ 間のマスは両端(二つの駒があるマス)を除いたマスです。
出発のマスから見るといつも遮られたことになります。
④ 斜めは |行の差| == |列の差| です。
絶対値を外すと片方向の斜めを見逃します。
⑤ a || b && c は八つの言語すべてで &&(Python は and)が先に結びつき、a || (b && c) になります。
クイーンは (まっすぐ || 斜め) && 間が空いている と括弧をつけます。
⑥ ナイトを「二つの差の和が 3」と書くとまっすぐ三マス離れた駒も当たりになり、キングを「二つの差の和が 1」と書くと斜めの隣を見逃します。
⑦ 攻撃はお互いさまではありません(ルークがナイトを攻撃しても、ナイトはそのルークを攻撃できないことがあります)。
攻撃するか(a, b) の引数の順を逆にすると「誰かが自分を」が「自分が誰かを」に変わります。

Pythonmain.py · 58 行
import sys  def straight(a, b):    """같은 가로줄이나 세로줄에 있는가"""    return a[0] == b[0] or a[1] == b[1]  def diagonal(a, b):    """같은 대각선에 있는가"""    return abs(a[0] - b[0]) == abs(a[1] - b[1])  def sign(x):    return (x > 0) - (x < 0)  def clear(board, a, b):    """a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가"""    dr, dc = sign(b[0] - a[0]), sign(b[1] - a[1])    r, c = a[0] + dr, a[1] + dc    while (r, c) != b:        if board[r][c] != ".":            return False        r, c = r + dr, c + dc    return True  def attacks(board, kind, a, b):    """a 칸에 있는 kind 말이 b 칸을 공격하는가"""    dr, dc = abs(a[0] - b[0]), abs(a[1] - b[1])    if kind == "N":        return (dr == 1 and dc == 2) or (dr == 2 and dc == 1)    if kind == "K":        return max(dr, dc) == 1    if kind == "R":        return straight(a, b) and clear(board, a, b)    if kind == "B":        return diagonal(a, b) and clear(board, a, b)    return (straight(a, b) or diagonal(a, b)) and clear(board, a, b)  def is_safe(board, pieces, target):    """다른 어떤 말도 target을 공격하지 않는가"""    return not any(attacks(board, board[p[0]][p[1]], p, target) for p in pieces if p != target)  lines = sys.stdin.read().split("\n")t = int(lines[0])at = 1out = []for _ in range(t):    h, w = map(int, lines[at].split())    board = [lines[at + 1 + r].strip() for r in range(h)]    at += 1 + h    pieces = [(r, c) for r in range(h) for c in range(w) if board[r][c] != "."]    out.append(sum(1 for p in pieces if is_safe(board, pieces, p)))print("\n".join(map(str, out)))
JavaScriptmain.js · 52 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 같은 가로줄이나 세로줄에 있는가const straight = (a, b) => a.r === b.r || a.c === b.c; // 같은 대각선에 있는가const diagonal = (a, b) => Math.abs(a.r - b.r) === Math.abs(a.c - b.c); // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가function clear(board, a, b) {  const dr = Math.sign(b.r - a.r), dc = Math.sign(b.c - a.c);  let r = a.r + dr, c = a.c + dc;  while (r !== b.r || c !== b.c) {    if (board[r][c] !== ".") return false;    r += dr;    c += dc;  }  return true;} // a 칸에 있는 말이 b 칸을 공격하는가function attacks(board, a, b) {  const dr = Math.abs(a.r - b.r), dc = Math.abs(a.c - b.c);  switch (board[a.r][a.c]) {    case "N": return (dr === 1 && dc === 2) || (dr === 2 && dc === 1);    case "K": return Math.max(dr, dc) === 1;    case "R": return straight(a, b) && clear(board, a, b);    case "B": return diagonal(a, b) && clear(board, a, b);    default: return (straight(a, b) || diagonal(a, b)) && clear(board, a, b);  }} // 다른 어떤 말도 target을 공격하지 않는가const isSafe = (board, pieces, target) =>  !pieces.some((p) => p !== target && attacks(board, p, target)); const t = Number(lines[0]);let at = 1;const out = [];for (let k = 0; k < t; k++) {  const [h, w] = lines[at].trim().split(" ").map(Number);  const board = lines.slice(at + 1, at + 1 + h).map((s) => s.trim());  at += 1 + h;  const pieces = [];  for (let r = 0; r < h; r++) {    for (let c = 0; c < w; c++) {      if (board[r][c] !== ".") pieces.push({ r, c });    }  }  out.push(pieces.filter((p) => isSafe(board, pieces, p)).length);}console.log(out.join("\n"));
TypeScriptmain.ts · 56 行
import { readFileSync } from "node:fs"; type Cell = { r: number; c: number }; const lines: string[] = readFileSync(0, "utf8").split("\n"); // 같은 가로줄이나 세로줄에 있는가const straight = (a: Cell, b: Cell): boolean => a.r === b.r || a.c === b.c; // 같은 대각선에 있는가const diagonal = (a: Cell, b: Cell): boolean => Math.abs(a.r - b.r) === Math.abs(a.c - b.c); // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가function clear(board: string[], a: Cell, b: Cell): boolean {  const dr = Math.sign(b.r - a.r), dc = Math.sign(b.c - a.c);  let r = a.r + dr, c = a.c + dc;  while (r !== b.r || c !== b.c) {    if (board[r][c] !== ".") return false;    r += dr;    c += dc;  }  return true;} // a 칸에 있는 말이 b 칸을 공격하는가function attacks(board: string[], a: Cell, b: Cell): boolean {  const dr = Math.abs(a.r - b.r), dc = Math.abs(a.c - b.c);  switch (board[a.r][a.c]) {    case "N": return (dr === 1 && dc === 2) || (dr === 2 && dc === 1);    case "K": return Math.max(dr, dc) === 1;    case "R": return straight(a, b) && clear(board, a, b);    case "B": return diagonal(a, b) && clear(board, a, b);    default: return (straight(a, b) || diagonal(a, b)) && clear(board, a, b);  }} // 다른 어떤 말도 target을 공격하지 않는가const isSafe = (board: string[], pieces: Cell[], target: Cell): boolean =>  !pieces.some((p) => p !== target && attacks(board, p, target)); const t = Number(lines[0]);let at = 1;const out: number[] = [];for (let k = 0; k < t; k++) {  const [h, w] = lines[at].trim().split(" ").map(Number);  const board = lines.slice(at + 1, at + 1 + h).map((s) => s.trim());  at += 1 + h;  const pieces: Cell[] = [];  for (let r = 0; r < h; r++) {    for (let c = 0; c < w; c++) {      if (board[r][c] !== ".") pieces.push({ r, c });    }  }  out.push(pieces.filter((p) => isSafe(board, pieces, p)).length);}console.log(out.join("\n"));
JavaMain.java · 71 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; public class Main {    record Cell(int r, int c) {}     static String[] board;     // 같은 가로줄이나 세로줄에 있는가    static boolean straight(Cell a, Cell b) {        return a.r() == b.r() || a.c() == b.c();    }     // 같은 대각선에 있는가    static boolean diagonal(Cell a, Cell b) {        return Math.abs(a.r() - b.r()) == Math.abs(a.c() - b.c());    }     // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가    static boolean clear(Cell a, Cell b) {        int dr = Integer.signum(b.r() - a.r()), dc = Integer.signum(b.c() - a.c());        int r = a.r() + dr, c = a.c() + dc;        while (r != b.r() || c != b.c()) {            if (board[r].charAt(c) != '.') return false;            r += dr;            c += dc;        }        return true;    }     // a 칸에 있는 말이 b 칸을 공격하는가    static boolean attacks(Cell a, Cell b) {        int dr = Math.abs(a.r() - b.r()), dc = Math.abs(a.c() - b.c());        return switch (board[a.r()].charAt(a.c())) {            case 'N' -> (dr == 1 && dc == 2) || (dr == 2 && dc == 1);            case 'K' -> Math.max(dr, dc) == 1;            case 'R' -> straight(a, b) && clear(a, b);            case 'B' -> diagonal(a, b) && clear(a, b);            default -> (straight(a, b) || diagonal(a, b)) && clear(a, b);        };    }     // 다른 어떤 말도 target을 공격하지 않는가    static boolean isSafe(List<Cell> pieces, Cell target) {        return pieces.stream().noneMatch(p -> !p.equals(target) && attacks(p, target));    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int t = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int k = 0; k < t; k++) {            String[] hw = in.readLine().trim().split(" ");            int h = Integer.parseInt(hw[0]), w = Integer.parseInt(hw[1]);            board = new String[h];            List<Cell> pieces = new ArrayList<>();            for (int r = 0; r < h; r++) {                board[r] = in.readLine().trim();                for (int c = 0; c < w; c++) {                    if (board[r].charAt(c) != '.') pieces.add(new Cell(r, c));                }            }            long safe = pieces.stream().filter(p -> isSafe(pieces, p)).count();            out.append(safe).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 70 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    static string[] board;     // 같은 가로줄이나 세로줄에 있는가    static bool Straight((int r, int c) a, (int r, int c) b) => a.r == b.r || a.c == b.c;     // 같은 대각선에 있는가    static bool Diagonal((int r, int c) a, (int r, int c) b) => Math.Abs(a.r - b.r) == Math.Abs(a.c - b.c);     // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가    static bool Clear((int r, int c) a, (int r, int c) b)    {        int dr = Math.Sign(b.r - a.r), dc = Math.Sign(b.c - a.c);        int r = a.r + dr, c = a.c + dc;        while (r != b.r || c != b.c)        {            if (board[r][c] != '.') return false;            r += dr;            c += dc;        }        return true;    }     // a 칸에 있는 말이 b 칸을 공격하는가    static bool Attacks((int r, int c) a, (int r, int c) b)    {        int dr = Math.Abs(a.r - b.r), dc = Math.Abs(a.c - b.c);        switch (board[a.r][a.c])        {            case 'N': return (dr == 1 && dc == 2) || (dr == 2 && dc == 1);            case 'K': return Math.Max(dr, dc) == 1;            case 'R': return Straight(a, b) && Clear(a, b);            case 'B': return Diagonal(a, b) && Clear(a, b);            default: return (Straight(a, b) || Diagonal(a, b)) && Clear(a, b);        }    }     // 다른 어떤 말도 target을 공격하지 않는가    static bool IsSafe(List<(int r, int c)> pieces, (int r, int c) target) =>        !pieces.Any(p => p != target && Attacks(p, target));     static void Main()    {        int t = int.Parse(Console.ReadLine().Trim());        var output = new StringBuilder();        for (int k = 0; k < t; k++)        {            int[] hw = Console.ReadLine().Trim().Split(' ').Select(int.Parse).ToArray();            int h = hw[0], w = hw[1];            board = new string[h];            var pieces = new List<(int r, int c)>();            for (int r = 0; r < h; r++)            {                board[r] = Console.ReadLine().Trim();                for (int c = 0; c < w; c++)                {                    if (board[r][c] != '.') pieces.Add((r, c));                }            }            output.Append(pieces.Count(p => IsSafe(pieces, p))).Append('\n');        }        Console.Write(output);    }}
C++main.cpp · 67 行
#include <algorithm>#include <cstdlib>#include <iostream>#include <string>#include <vector> struct Cell {    int r, c;    bool operator==(const Cell& o) const { return r == o.r && c == o.c; }}; using Board = std::vector<std::string>; // 같은 가로줄이나 세로줄에 있는가bool straight(Cell a, Cell b) { return a.r == b.r || a.c == b.c; } // 같은 대각선에 있는가bool diagonal(Cell a, Cell b) { return std::abs(a.r - b.r) == std::abs(a.c - b.c); } int sign(int x) { return (x > 0) - (x < 0); } // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가bool clear(const Board& board, Cell a, Cell b) {    const int dr = sign(b.r - a.r), dc = sign(b.c - a.c);    for (int r = a.r + dr, c = a.c + dc; r != b.r || c != b.c; r += dr, c += dc) {        if (board[r][c] != '.') return false;    }    return true;} // a 칸에 있는 말이 b 칸을 공격하는가bool attacks(const Board& board, Cell a, Cell b) {    const int dr = std::abs(a.r - b.r), dc = std::abs(a.c - b.c);    switch (board[a.r][a.c]) {    case 'N': return (dr == 1 && dc == 2) || (dr == 2 && dc == 1);    case 'K': return std::max(dr, dc) == 1;    case 'R': return straight(a, b) && clear(board, a, b);    case 'B': return diagonal(a, b) && clear(board, a, b);    default: return (straight(a, b) || diagonal(a, b)) && clear(board, a, b);    }} // 다른 어떤 말도 target을 공격하지 않는가bool isSafe(const Board& board, const std::vector<Cell>& pieces, Cell target) {    return std::none_of(pieces.begin(), pieces.end(),                        [&](Cell p) { return !(p == target) && attacks(board, p, target); });} int main() {    int t;    std::cin >> t;    while (t-- > 0) {        int h, w;        std::cin >> h >> w;        Board board(h);        std::vector<Cell> pieces;        for (int r = 0; r < h; r++) {            std::cin >> board[r];            for (int c = 0; c < w; c++) {                if (board[r][c] != '.') pieces.push_back({r, c});            }        }        std::cout << std::count_if(pieces.begin(), pieces.end(),                                   [&](Cell p) { return isSafe(board, pieces, p); })                  << '\n';    }}
Cmain.c · 65 行
#include <stdbool.h>#include <stdio.h>#include <stdlib.h> typedef struct { int r, c; } Cell; static char board[12][13]; /* 같은 가로줄이나 세로줄에 있는가 */static bool straight(Cell a, Cell b) { return a.r == b.r || a.c == b.c; } /* 같은 대각선에 있는가 */static bool diagonal(Cell a, Cell b) { return abs(a.r - b.r) == abs(a.c - b.c); } static int sign(int x) { return (x > 0) - (x < 0); } /* a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가 */static bool clear(Cell a, Cell b) {    int dr = sign(b.r - a.r), dc = sign(b.c - a.c);    for (int r = a.r + dr, c = a.c + dc; r != b.r || c != b.c; r += dr, c += dc) {        if (board[r][c] != '.') return false;    }    return true;} /* a 칸에 있는 말이 b 칸을 공격하는가 */static bool attacks(Cell a, Cell b) {    int dr = abs(a.r - b.r), dc = abs(a.c - b.c);    switch (board[a.r][a.c]) {    case 'N': return (dr == 1 && dc == 2) || (dr == 2 && dc == 1);    case 'K': return (dr > dc ? dr : dc) == 1;    case 'R': return straight(a, b) && clear(a, b);    case 'B': return diagonal(a, b) && clear(a, b);    default: return (straight(a, b) || diagonal(a, b)) && clear(a, b);    }} /* 다른 어떤 말도 pieces[target]을 공격하지 않는가 */static bool is_safe(const Cell *pieces, int count, int target) {    for (int i = 0; i < count; i++) {        if (i != target && attacks(pieces[i], pieces[target])) return false;    }    return true;} int main(void) {    int t;    scanf("%d", &t);    while (t-- > 0) {        int h, w, count = 0, safe = 0;        Cell pieces[40];        scanf("%d %d", &h, &w);        for (int r = 0; r < h; r++) {            scanf("%12s", board[r]);            for (int c = 0; c < w; c++) {                if (board[r][c] != '.') pieces[count++] = (Cell){r, c};            }        }        for (int i = 0; i < count; i++) {            if (is_safe(pieces, count, i)) safe++;        }        printf("%d\n", safe);    }    return 0;}
Craykonmain.cray · 111 行
판 = [];// 말이 있는 칸의 줄 번호와 칸 번호줄들 = [];칸들 = []; 부호(x){    もし (x > 0)    {        返す 1;    }    もし (x < 0)    {        返す -1;    }    返す 0;} // 같은 가로줄이나 세로줄에 있는가곧은줄인가(a, b){    返す 줄들[a] == 줄들[b] || 칸들[a] == 칸들[b];} // 같은 대각선에 있는가대각선인가(a, b){    返す 数学.絶対値(줄들[a] - 줄들[b]) == 数学.絶対値(칸들[a] - 칸들[b]);} // a와 b 사이(양 끝은 빼고)에 말이 하나도 없는가비었나(a, b){    dr = 부호(줄들[b] - 줄들[a]);    dc = 부호(칸들[b] - 칸들[a]);    r = 줄들[a] + dr;    c = 칸들[a] + dc;    間 (r != 줄들[b] || c != 칸들[b])    {        もし (판[r][c] != ".")        {            返す 偽;        }        r += dr;        c += dc;    }    返す 真;} // a번 말이 b번 말을 공격하는가공격하나(a, b){    dr = 数学.絶対値(줄들[a] - 줄들[b]);    dc = 数学.絶対値(칸들[a] - 칸들[b]);    選択 (판[줄들[a]][칸들[a]])    {        場合 "N":            返す dr == 1 && dc == 2 || dr == 2 && dc == 1;        場合 "K":            返す 数学.大きい方(dr, dc) == 1;        場合 "R":            返す 곧은줄인가(a, b) && 비었나(a, b);        場合 "B":            返す 대각선인가(a, b) && 비었나(a, b);    }    返す (곧은줄인가(a, b) || 대각선인가(a, b)) && 비었나(a, b);} // 다른 어떤 말도 b번 말을 공격하지 않는가안전한가(b){    繰り返す (i = 0; i < 줄들.個数; i++)    {        もし (i != b && 공격하나(i, b))        {            返す 偽;        }    }    返す 真;} t = コンソール.行読み().数に();繰り返す (k = 0; k < t; k++){    hw = コンソール.数読み();    판 = [];    줄들 = [];    칸들 = [];    繰り返す (r = 0; r < hw[0]; r++)    {        줄 = コンソール.行読み().空白除去();        판.追加(줄);        繰り返す (c = 0; c < hw[1]; c++)        {            もし (줄[c] != ".")            {                줄들.追加(r);                칸들.追加(c);            }        }    }    안전 = 0;    繰り返す (i = 0; i < 줄들.個数; i++)    {        もし (안전한가(i))        {            안전++;        }    }    コンソール.書く(안전);}