#️⃣문제
캐슬 디펜스는 성을 향해 몰려오는 적을 잡는 턴 방식의 게임이다. 게임이 진행되는 곳은 크기가 N x M인 격자판으로 나타낼 수 있다. 격자판은 1 x 1 크기의 칸으로 나누어져 있고, 각 칸에 포함된 적의 수는 최대 하나이다. 격자판의 N번행의 바로 아래 (N + 1)번 행의 모든 칸에는 성이 있다.
성을 적에게서 지키기 위해 궁수 3명을 배치하려고 한다. 궁수는 성이 있는 칸에 배치할 수 있고, 하나의 칸에는 최대 1명의 궁수만 있을 수 있다. 각각의 턴마다 궁수는 적 하나를 공격할 수 있고, 모든 궁수는 동시에 공격한다. 궁수가 공격한느 적은 거리가 D이하인 적 중에서 가장 가까운 적이고, 그러한 적이 여럿일 경우에는 가장 왼쪽에 있는 적을 공격한다. 같은 적이 여러 궁수에게 공격당할 수 있다. 공격받은 적은 게임에서 제외된다. 궁수의 공격이 끝나면, 적이 이동한다. 적은 아래로 한 칸 이동하며, 성이 있는 칸으로 이동한 경우에는 게임에서 제외된다. 모든 적이 격자판에서 제외되면 게임이 끝난다.
게임 설명에서 보다시피 궁수를 배치한 이후의 게임 진행은 정해져있다. 따라서, 이 게임은 궁수의 위치가 중요하다. 격자판의 상태가 주어졌을 때, 궁수의 공격으로 제거할 수 있는 적의 최대 수를 계산해보자.
격자판의 두 위치 (r1, c1), (r2, c2)의 거리는 |r1 - r2| + |c1 - c2|이다.
#️⃣ 입력
첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.
#️⃣ 출력
첫째 줄에 궁수의 공격으로 제거할 수 있는 적의 최대 수를 출력한다.
✅ 풀이 Point
1️⃣ 게임 진행 순서
💡궁수와의 거리가 D 이하인 적 중에 거리가 가장 짧은 적 공격
→ 거리가 가장 짧은 적이 여러 명인 경우 가장 왼쪽의 적 공격
우선 순위 : 거리 → 좌측에 위치한 적
- 궁수 3명 동시에 공격
→ 적 아래로 한 칸 이동(이동한 칸이 성일 경우 해당 적 제외)
2️⃣ 궁수의 배치에 따라 적을 죽일 수 있는 최대치가 다르기 때문에 궁수의 배치 조합을 구해야 함.
- 궁수의 수 3명 고정 → R = 3
- int[ ] order = new int[R] → 궁수 3명의 x 좌표가 든 배열
- 재귀로 구현, 재귀로 넘어갈 때 idx를 같이 넘겨서 중복 방지
private static void comb(int k, int s) { // 15 * 14 * 13 / 6 = 455 충분히 가능함.
if (R == k) {
ans = Math.max(ans, solution());
} else {
for (int i = s; i < M; i++) {
order[k] = i;
comb(k + 1, i + 1);
}
}
}
3️⃣ 각 궁수가 죽일 수 있는 가능한 모든 적을 아래와 같은 정렬 기준을 가진 우선순위 큐에 넣어서 가장 죽일 가능성이 높은 적을 0번 인덱스에 위치하게 함.
- class Node / 1순위 : 궁수와 적의 거리 오름차순, 2순위 : 적의 x좌표 오름차순
static class Node implements Comparable<Node> {
int i;
int j;
int d; // 가중치
public Node(int i, int j, int d) {
this.i = i;
this.j = j;
this.d = d;
}
@Override
public int compareTo(Node o) {
if (this.d == o.d) return this.j - o.j; // 거리가 같다면 더 왼쪽에 위치한 것으로 정렬
return this.d - o.d;
}
}
- enemyCandidates가 존재하면 가장 앞의 원소를 빼서 해당 턴에 실제로 궁수가 죽일 적이 들어가는 enemies 배열에 넣어줌.
List<Node> enemies = new ArrayList<>();
for (int k = 0; k < R; k++) {
PriorityQueue<Node> enemyCandidates = new PriorityQueue<>(); // 각 궁수가 죽일 수 있는 적 후보
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// 적이 존재하고 적과의 거리 D 이하일 때,
if (arrSol[i][j] == 1) {
if (((N - i) + Math.abs(order[k] - j)) <= D) {
enemyCandidates.offer(new Node(i, j, (N - i) + Math.abs(order[k] - j)));
}
}
}
}
if (!enemyCandidates.isEmpty()) {
Node node = enemyCandidates.poll();
enemies.add(node);
}
}
- 중복된 적을 죽일 수 있으므로 enemies 배열의 적을 제거하기 전에 현재 배열에 적이 존재하는지 확인 필요함.
for (Node enemy : enemies) { // 궁수, 적 제거
if (arrSol[enemy.i][enemy.j] == 1) {
arrSol[enemy.i][enemy.j] = 0;
cnt++;
}
}
4️⃣ 적 제거 과정까지 거친 후 적이 이동하고 arrSol에 적이 남아있어 위의 과정을 반복해야하는지 확인.
boolean temp = false; // arrSol에 적이 아직 남아있어 while문을 반복해야하는지 판단
for (int j = 0; j < M; j++) arrSol[N - 1][j] = 0;
for (int i = N - 2; i > -1; i--) { // 적 아래로 한 칸 이동
for (int j = 0; j < M; j++) {
if (arrSol[i][j] == 1) {
arrSol[i][j] = 0;
arrSol[i + 1][j] = 1;
temp = true;
}
}
}
✅ 정답
import java.io.*;
import java.util.*;
public class Main {
static int N, M, D;
static int R = 3; // 조합 mCr
static int[][] arr;
static int[] order; // 궁수의 x좌표를 담은 배열
static int ans = 0;
static class Node implements Comparable<Node> {
int i;
int j;
int d; // 가중치
public Node(int i, int j, int d) {
this.i = i;
this.j = j;
this.d = d;
}
@Override
public int compareTo(Node o) {
if (this.d == o.d) return this.j - o.j; // 거리가 같다면 더 왼쪽에 위치한 것으로 정렬
return this.d - o.d;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
D = Integer.parseInt(st.nextToken());
arr = new int[N][M];
order = new int[R];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < M; j++) arr[i][j] = Integer.parseInt(st.nextToken());
}
comb(0, 0);
System.out.println(ans);
}
private static void comb(int k, int s) { // 15 * 14 * 13 / 6 = 455 충분히 가능함.
if (R == k) {
ans = Math.max(ans, solution());
} else {
for (int i = s; i < M; i++) {
order[k] = i;
comb(k + 1, i + 1);
}
}
}
private static int solution() {
// 원본 배열 유지를 위한 복사
int[][] arrSol = new int[N][M];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) arrSol[i][j] = arr[i][j];
}
int cnt = 0;
boolean flag = true;
while (flag) {
List<Node> enemies = new ArrayList<>();
for (int k = 0; k < R; k++) {
PriorityQueue<Node> enemyCandidates = new PriorityQueue<>(); // 각 궁수가 죽일 수 있는 적 후보
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// 적이 존재하고 적과의 거리 D 이하일 때,
if (arrSol[i][j] == 1) {
if (((N - i) + Math.abs(order[k] - j)) <= D) {
enemyCandidates.offer(new Node(i, j, (N - i) + Math.abs(order[k] - j)));
}
}
}
}
if (!enemyCandidates.isEmpty()) {
Node node = enemyCandidates.poll();
enemies.add(node);
}
}
for (Node enemy : enemies) { // 궁수, 적 제거
if (arrSol[enemy.i][enemy.j] == 1) {
arrSol[enemy.i][enemy.j] = 0;
cnt++;
}
}
boolean temp = false; // arrSol에 적이 아직 남아있어 while문을 반복해야하는지 판단
for (int j = 0; j < M; j++) arrSol[N - 1][j] = 0;
for (int i = N - 2; i > -1; i--) { // 적 아래로 한 칸 이동
for (int j = 0; j < M; j++) {
if (arrSol[i][j] == 1) {
arrSol[i][j] = 0;
arrSol[i + 1][j] = 1;
temp = true;
}
}
}
if (!temp) {
flag = false;
}
}
return cnt;
}
}
'Algorithm > Java' 카테고리의 다른 글
[백준 : 골드 4] Java BJ1261 알고스팟 (1) | 2024.03.13 |
---|---|
[백준 : 실버 2] Java BJ1541 잃어버린 괄호 (0) | 2024.03.11 |
[백준 : 골드 4] BJ1647 도시 분할 계획 (0) | 2024.03.06 |
[백준 : 골드 4] Java BJ5052 전화번호 목록 (0) | 2024.03.03 |
[백준 : 실버 1] Java BJ1946 신입사원 (0) | 2024.03.01 |