{ }codelper
判断函数二维列表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))        {            안전++;        }    }    控制台.输出(안전);}