https://www.acmicpc.net/problem/19236
- 구현, 시뮬레이션
- 백트래킹, 완전 탐색
- 각 분기에서 상어의 방향 일직선 상으로 이동 가능한 칸 개수 = 최대 3개
int[][] map
Fish[] fishes
: 1 ~ 16번 물고기 정보
※ Fish
: 물고기 위치 (y, x)
, 번호 idx
, 방향 dir
, 먹힘 여부 alive
각 분기에서 상어가 이동할 위치 선택: 최대 3개 경우의 수
최대 확인해야 하는 경우의 수 = 3^15 = 14,348,907 << 1억
=> 완전 탐색 가능
import java.io.*;
import java.util.*;
class Fish {
public int y, x; // 물고기 위치
public int idx, dir; // 물고기 번호: 1 ~ 16, 방향: 1 ~ 8
public boolean alive; // 물고기가 먹혔는지 여부
public Fish(int y, int x, int idx, int dir, boolean alive) {
this.y = y;
this.x = x;
this.idx = idx;
this.dir = dir;
this.alive = alive;
}
}
public class Main {
static int[][] map;
static Fish[] fishes;
static int maxEatSum; // 출력
// 반시계 방향 순서, index [1] ~ [8] 사용: { 제자리, ↑, ↖, ←, ↙, ↓, ↘, →, ↗ }
static int[] dy = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
static int[] dx = { 0, 0, -1, -1, -1, 0, 1, 1, 1 };
static final int EMPTY = 0, SHARK = -1;
static void backtrack(int sharkY, int sharkX, int shakrDir, int eatSum) {
int[][] tempMap = new int[4][4]; // Copy - map을 되돌려놓기 위해 백업
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
tempMap[i][j] = map[i][j];
}
}
Fish[] tempFishes = new Fish[17]; // Copy - fishes를 되돌려놓기 위해 백업
for (int i = 1; i <= 16; i++) {
Fish fish = fishes[i];
tempFishes[i] = new Fish(fish.y, fish.x, fish.idx, fish.dir, fish.alive);
}
// 물고기 번호 순으로 차례로 이동
moveAllFishes();
// 상어의 방향 일직선 상에서, 물고기 칸을 선택
for (int i = 1; i <= 3; i++) { // 최대 3개 경우의 수
int ny = sharkY + (dy[shakrDir] * i);
int nx = sharkX + (dx[shakrDir] * i);
if (!isValid(ny, nx))
continue;
if (1 <= map[ny][nx] && map[ny][nx] <= 16) { // 이동 선택할 다음 칸이 물고기 칸인 경우
int fishIdx = map[ny][nx]; // 잡아먹힐 물고기 번호
int nDir = fishes[fishIdx].dir; // 상어의 다음 방향 = 먹은 물고기의 방향
// 상어가 물고기 칸으로 이동해서 물고기 먹음
map[sharkY][sharkX] = EMPTY; // 기존에 상어가 있던 칸
map[ny][nx] = SHARK;
fishes[fishIdx].alive = false;
maxEatSum = Math.max(maxEatSum, eatSum + fishIdx);
backtrack(ny, nx, nDir,eatSum + fishIdx);
// backtrack
map[ny][nx] = fishIdx;
map[sharkY][sharkX] = SHARK;
fishes[fishIdx].alive = true;
}
}
map = tempMap;
fishes = tempFishes;
}
static void moveAllFishes() {
for (int i = 1; i <= 16; i++) {
// 아직 상어에게 잡아먹히지 않은 남은 물고기인 경우
if (fishes[i].alive) {
moveFish(i);
}
}
}
static void moveFish(int fishIdx) {
int y = fishes[fishIdx].y;
int x = fishes[fishIdx].x;
int dir = fishes[fishIdx].dir;
// 현재 방향부터 반시계 방향으로
for (int i = 0; i <= 8; i++) {
int nDir = (dir + i) % 9; // i = 0이면, nDir = 현재 방향 dir과 동일
if (nDir == 0) // 본인 제자리 칸은 제외
continue;
fishes[fishIdx].dir = nDir; // 물고기 방향 회전 반영
int ny = y + dy[nDir];
int nx = x + dx[nDir];
if (!isValid(ny, nx))
continue;
if (map[ny][nx] == EMPTY) { // 인접 칸이 빈 칸인 경우
map[y][x] = EMPTY; // 기존 칸
map[ny][nx] = fishIdx; // 새로 이동하는 칸
fishes[fishIdx].y = ny;
fishes[fishIdx].x = nx;
break;
}
else if (1 <= map[ny][nx] && map[ny][nx] <= 16) { // 인접 칸이 물고기 칸인 경우
// 물고기 칸끼리 서로 위치 swap
int nFishIdx = map[ny][nx];
map[ny][nx] = fishIdx;
fishes[fishIdx].y = ny;
fishes[fishIdx].x = nx;
map[y][x] = nFishIdx;
fishes[nFishIdx].y = y;
fishes[nFishIdx].x = x;
break;
}
}
}
static boolean isValid(int y, int x) {
return (0 <= y && y < 4) && (0 <= x && x < 4);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
StringTokenizer st;
map = new int[4][4];
fishes = new Fish[17]; // [1] ~ [16] 사용
for (int i = 0; i < 4; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < 4; j++) {
int idx = Integer.parseInt(st.nextToken());
int dir = Integer.parseInt(st.nextToken());
map[i][j] = idx;
fishes[idx] = new Fish(i, j, idx, dir, true);
}
}
// 최초: 상어가 [0][0] 위치의 물고기를 먹고, 먹은 물고기 방향을 가짐
int fishIdx = map[0][0];
int sharkDir = fishes[fishIdx].dir; // 상어 방향 = 먹은 물고기 방향
map[0][0] = SHARK;
fishes[fishIdx].alive = false;
fishes[fishIdx].y = -1;
fishes[fishIdx].x = -1;
fishes[fishIdx].dir = -1;
maxEatSum += fishIdx;
backtrack(0, 0, sharkDir, maxEatSum); // Init Call
System.out.println(maxEatSum);
}
}