{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

'~인가?'를 묻는 일을 참·거짓을 돌려주는 작은 함수로 나누면(같은 줄인가, 대각선인가, 사이가 비었나, 공격하나, 안전한가) 복잡한 조건이 이름 붙은 질문의 조합으로 읽혀요.
돌려주는 타입은 Python bool(True/False), JS·TS boolean, Java boolean, C#·C++ bool, C는 <stdbool.h>의 bool, 크레이콘은 참/거짓이에요.
'하나라도 있나'는 Python any(...), JS·TS some, Java 스트림 anyMatch/noneMatch, C# Any, C++ std::any_of/std::none_of로 쓰고, C와 크레이콘은 반복문 안에서 찾자마자 돌려주기로 끝내요.
절댓값은 Python·C abs, C++ std::abs, JS·TS·Java Math.abs, C# Math.Abs, 크레이콘 수학.절댓값이고, 부호는 JS·TS Math.sign, Java Integer.signum, C# Math.Sign이 있지만 Python·C·C++·크레이콘에는 정수용 부호 함수가 없어서 직접 써요.
함정 ① 룩·비숍·퀸은 사이에 낀 말에 막혀요.
방향만 보고 끝내면 안 돼요.
② 사이를 살피는 반복문에서 첫 칸만 보고 곧바로 참을 돌려주는 실수: 참은 반복이 다 끝난 뒤에만 돌려줘요.
③ 사이 칸은 양 끝(두 말이 있는 칸)을 뺀 칸이에요.
출발 칸부터 보면 언제나 막힌 것이 돼요.
④ 대각선은 |줄 차| == |칸 차|예요.
절댓값을 빼면 한쪽 방향 대각선을 놓쳐요.
⑤ 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))        {            안전++;        }    }    콘솔.쓰기(안전);}