문제 출처: https://www.acmicpc.net/problem/17135
문제
캐슬 디펜스는 성을 향해 몰려오는 적을 잡는 턴 방식의 게임이다. 게임이 진행되는 곳은 크기가 N×M인 격자판으로 나타낼 수 있다. 격자판은 1×1 크기의 칸으로 나누어져 있고, 각 칸에 포함된 적의 수는 최대 하나이다. 격자판의 N번행의 바로 아래(N+1번 행)의 모든 칸에는 성이 있다.
성을 적에게서 지키기 위해 궁수 3명을 배치하려고 한다. 궁수는 성이 있는 칸에 배치할 수 있고, 하나의 칸에는 최대 1명의 궁수만 있을 수 있다. 각각의 턴마다 궁수는 적 하나를 공격할 수 있고, 모든 궁수는 동시에 공격한다. 궁수가 공격하는 적은 거리가 D이하인 적 중에서 가장 가까운 적이고, 그러한 적이 여럿일 경우에는 가장 왼쪽에 있는 적을 공격한다. 같은 적이 여러 궁수에게 공격당할 수 있다. 공격받은 적은 게임에서 제외된다. 궁수의 공격이 끝나면, 적이 이동한다. 적은 아래로 한 칸 이동하며, 성이 있는 칸으로 이동한 경우에는 게임에서 제외된다. 모든 적이 격자판에서 제외되면 게임이 끝난다.
게임 설명에서 보다시피 궁수를 배치한 이후의 게임 진행은 정해져있다. 따라서, 이 게임은 궁수의 위치가 중요하다. 격자판의 상태가 주어졌을 때, 궁수의 공격으로 제거할 수 있는 적의 최대 수를 계산해보자.
격자판의 두 위치 (r1, c1), (r2, c2)의 거리는 |r1-r2| + |c1-c2|이다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
private static int N, M, D, enemyCnt, res;
private static int[][] map, directions = { { 0, -1 }, { -1, 0 }, { 0, 1 }, { 1, 0 } }; // 왼쪽 우선
private static class Node {
int y; // 행
int x; // 열
int distance; // 거리
public Node(int y, int x, int distance) {
this.y = y;
this.x = x;
this.distance = distance;
}
}
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
N = Integer.parseInt(tokenizer.nextToken()); // 행
M = Integer.parseInt(tokenizer.nextToken()); // 열
D = Integer.parseInt(tokenizer.nextToken()); // 궁수의 공격 거리 제한
map = new int[N + 1][M]; // 성의 위치까지 고려하여 행을 +1 해준다.
for (int i = 0; i < N; i++) {
tokenizer = new StringTokenizer(reader.readLine());
for (int j = 0; j < M; j++) {
map[i][j] = Integer.parseInt(tokenizer.nextToken());
if (map[i][j] == 1) enemyCnt++;
}
}
combination(new int[3], 0, 0);
System.out.println(res);
}
private static void combination(int[] target, int start, int cnt) {
if (cnt == 3) {
int removeCnt = 0;
int totalCnt = enemyCnt;
int[][] arr = new int[N + 1][M];
for (int i = 0; i <= N; i++) {
arr[i] = map[i].clone();
}
while (totalCnt != 0) {
List<Node> removeList = new ArrayList<>();
// 궁수 3명 배치
// 가장 가까운 적 찾기(단, 궁수들이 같은 적을 타겟으로 삼을 수도 있음)
// bfs
for (int i = 0; i < 3; i++) {
Node temp;
if ((temp = bfs(new Node(N, target[i], 0), arr)) != null) {
// 적을 잡은 경우
removeList.add(temp);
}
}
// 적 제거 처리
for (Node node : removeList) {
if (arr[node.y][node.x] == 1) {
arr[node.y][node.x] = 0;
removeCnt++;
totalCnt--;
}
}
// 적들의 진격
int[][] temp = new int[N + 1][M];
for (int i = 1; i < N; i++) {
temp[i] = arr[i - 1].clone();
}
int sum = 0;
for (int i = 0; i < M; i++) {
sum += arr[N - 1][i];
}
totalCnt -= sum;
arr = temp;
}
res = Math.max(res, removeCnt);
return;
}
for (int i = start; i < M; i++) {
target[cnt] = i;
combination(target, i + 1, cnt + 1);
}
}
private static Node bfs(Node start, int[][] arr) {
Queue<Node> queue = new ArrayDeque<>();
queue.offer(start);
boolean[][] visited = new boolean[N + 1][M];
while (!queue.isEmpty()) {
Node curr = queue.poll();
int y = curr.y;
int x = curr.x;
int dist = curr.distance;
if (visited[y][x]) continue;
if (dist <= D && arr[y][x] == 1) return curr;
if (dist > D) return null;
visited[y][x] = true;
for (int i = 0; i < 4; i++) {
int dy = y + directions[i][0];
int dx = x + directions[i][1];
if (dy >= 0 && dy < N && dx >= 0 && dx < M) {
if (!visited[dy][dx]) {
queue.offer(new Node(dy, dx, dist + 1));
}
}
}
}
return null;
}
}