https://www.acmicpc.net/problem/23290

 

23290번: 마법사 상어와 복제

첫째 줄에 물고기의 수 M, 상어가 마법을 연습한 횟수 S가 주어진다. 둘째 줄부터 M개의 줄에는 물고기의 정보 fx, fy, d가 주어진다. (fx, fy)는 물고기의 위치를 의미하고, d는 방향을 의미한다. 방향

www.acmicpc.net

코드설명

구현(Implementation) + 시뮬레이션(Simulation) 문제입니다.

 

전체적인 코드 로직입니다.

  1. 물고기의 이동: 모든 물고기가 한 칸씩 이동하면서 격자의 범위를 벗어나거나 상어가 있는 칸으로는 이동하지 않도록 조건을 체크합니다. 물고기는 자신이 가지고 있는 이동 방향이 이동할 수 있는 칸을 향할 때까지 방향을 45도 반시계 회전시킵니다.
  2. 상어의 이동과 먹이 찾기: 상어는 연속해서 3칸을 이동합니다. 가능한 이동 방법 중에서 제외되는 물고기의 수가 가장 많은 방법을 선택하여 최대로 먹을 수 있는 물고기 수를 찾습니다. 그 후, 해당 방법으로 상어를 이동시켜 실제로 물고기를 먹습니다.
  3. 물고기의 냄새 갱신: 물고기가 사라진 위치에 냄새를 남기고, 3번 과정 직후에 격자에서 사라지도록 냄새를 갱신합니다.
  4. 복제된 물고기 복원: 1번에서 사용한 복제마법이 완료되면, 복제된 물고기를 실제 위치에 복원합니다.
  5. 반복: 주어진 시간(S)만큼 1~4의 과정을 반복하며 최종적으로 먹을 수 있는 물고기 수를 계산합니다.

문제에서 실제 구현시 제가 겪었던 오류사항 혹은 주의해야할점들입니다.

1. 문제 조건 중 아래의 조건입니다. (문제에서는 2번조건)

  1. 모든 물고기가 한 칸 이동한다. 상어가 있는 칸, 물고기의 냄새가 있는 칸, 격자의 범위를 벗어나는 칸으로는 이동할 수 없다. 각 물고기는 자신이 가지고 있는 이동 방향이 이동할 수 있는 칸을 향할 때까지 방향을 45도 반시계 회전시킨다. 만약, 이동할 수 있는 칸이 없으면 이동을 하지 않는다. 그 외의 경우에는 그 칸으로 이동을 한다. 물고기의 냄새는 아래 3에서 설명한다.

이때, 이동할 수 있는 칸이 없으면 이동을 하지 않는다는 부분에서  originDir을 사용하여 원래 방향으로 돌려주든, 혹은

while(!fishMap[i][j].isEmpty()) {
boolean fishMoveFlag = false;
Fish temp = fishMap[i][j].poll();
int originDir = temp.dir;
for(int dir = 0; dir < 8; dir++) {
if(dir > 0) //처음 방향부터 먼저 검사합니다.
temp.dir = changeDirByAntiClock(temp.dir);
int nr = temp.r + dx[temp.dir];
int nc = temp.c + dy[temp.dir];
if(nr < 0 || nr >= 4 || nc < 0 || nc >= 4) continue; //격자의 범위를 벗어나는경우
if( nr == shark.r && nc == shark.c) continue; //상어가 존재하는 위치일경우
if( fishSmellVisited[nr][nc] > 0 ) continue; //물고기의 냄새가 남아있을경우
//한번 이동성공한다면 해당 물고기의 이동은 중단시킨다.
storeFishMap[nr][nc].offer(new Fish(nr, nc, temp.dir));
fishMoveFlag = true;
break;
}
if(fishMoveFlag == false) { //만약 이동할 수 없다면 이동을 하지 않는데, 그래도 위치에는 있어야한다.
storeFishMap[temp.r][temp.c].add(new Fish(temp.r, temp.c, originDir)); //이 부분에서 틀렸다.. 원래 방향으로유지해야햇다.
}
}

 

아래와 같이 dir을 한번 더 반시계방향으로 45도 돌려서 원래 방향으로 돌립니다.

if(fishMoveFlag == false) { //만약 이동할 수 없다면 이동을 하지 않는데, 그래도 위치에는 있어야한다.
temp.dir = changeDirByAntiClock(temp.dir);
storeFishMap[temp.r][temp.c].add(new Fish(temp.r, temp.c, temp.dir)); //이 부분에서 틀렸다.. 원래 방향으로유지해야햇다.
}

 

구현할시에 현재 방향을 유지하면서 이동하지 않는경우도 넣었어야했는데

temp.dir을 8번 회전시킨다는 생각에 당연히 원래 방향으로 돌아간다고 생각하였지만, 첫 경우에는 원래 방향으로 돌기에 결국은 7번회전하므로 마지막에 한번 더 돌려주는 작업이 필요합니다.

혹은 int nDir 을 활용해서 회전과 temp.dir을 분리하거나,

originDir을 활용해서 원래 방향을 유지할 수도 있습니다.

" temp. dir "이 원래 방향으로 돌아가지 않아 올바른 값을 찾지 못했습니다.

 

이런 구현시에는 반드시 조건을 하나씩 완성시켜야합니다.

 

두번쨰는, 상어는 원래 지났던 방향도 다시 갈 수 있다는 점이고, 상어가 물고기를 먹으면 물고기를 먹었다는 처리가 필요하다는 점입니다.

 

저는 fishSize를 활용해서 존재하는 물고기의 개수를 저장하고, 먹는다면 해당 칸의 물고기를 먹음 처리를 안했었습니다.

먹음 처리해줍니다.

int[][] fishSize = new int[4][4];
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
fishSize[i][j] = fishMap[i][j].size();
}
}
//만약 물고기를 만난다면 다 먹는다.
// for(Fish tempFish : fishMap[nsr][nsc]) {
// sharkEatFish += 1;
// }
sharkEatFish += fishSize[nsr][nsc];
fishSize[nsr][nsc] = 0;

 

또한 사전순으로 만들기 위해 방향대로 순열을 진행했고, maxSharkEat보다 커야만 해당 값이 갱신되도록 하여 사전순으로 되도록 처리했습니다.

 

자세한 설명은 모두 주석에 달아보았습니다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
public static int M, S;
public static int answer = 0;
public static int[] dx = {0, -1, -1, -1, 0, 1, 1, 1};
public static int[] dy = {-1, -1, 0, 1, 1, 1, 0, -1};
public static Shark shark;
public static int[][] fishSmellVisited = new int[4][4];
public static Queue<Fish>[][] fishMap = new Queue[4][4];
public static int[] sharkMove = new int[3];
public static int[] maxSharkEatMove = new int[3];
public static int maxSharkEat = 0;
public static int OUTOFBOUND = -1;
public static int SHARKMININITIAL = -1;
//상어의 이동방법을 사전 순으로 가장 앞서는 방법을 찾기 위해 다른 방향을 따로 도입한다.. 상 좌 하 우 순서이다. (반시계방향)
public static int[] sharkDx = {-1, 0, 1, 0};
public static int[] sharkDy = {0, -1, 0, 1};
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
M = Integer.parseInt(st.nextToken());
S = Integer.parseInt(st.nextToken());
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
fishMap[i][j] = new LinkedList<Fish>();
}
}
for(int i=0;i<M;i++) {
st = new StringTokenizer(br.readLine());
int fx = Integer.parseInt(st.nextToken());
int fy = Integer.parseInt(st.nextToken());
int d = Integer.parseInt(st.nextToken());
fishMap[fx - 1][fy - 1].add(new Fish(fx - 1, fy - 1, d - 1));
}
st = new StringTokenizer(br.readLine());
int sx = Integer.parseInt(st.nextToken());
int sy = Integer.parseInt(st.nextToken());
shark = new Shark(sx - 1, sy - 1);
while(--S >= 0){
//1. 상어가 모든 물고기에게 복제마법을 시전한다. 복제 마법은 시간이 조금 걸리기 때문에, 아래 5번에서 물고기가 복제되어 칸에 나타난다.
//Queue에 물고기들의 정보를 "복제"해서 넣어놓습니다.
Queue<Fish> copyFishQueue = new LinkedList<Fish>();
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
for(Fish temp : fishMap[i][j]) {
copyFishQueue.add(new Fish(temp.r, temp.c, temp.dir));
}
}
}
//2. 모든 물고기가 한칸 이동한다. 상어가 있는 칸, 물고기의 냄새가 있는 칸, 격자의 범위를 벗어나는 칸으로는 이동할 수 없다.
//각 물고기는 자신이 가지고 있는 이동 방향이 이동할 수 있는 칸을 향할때까지 방향을 45도 반시계 회전 시킨다.
//만약, 이동할 수 있는 칸이 없으면 이동을 하지 않는다.
//그외의 경우에는 그 칸으로 이동을 한다. 물고기의 냄새는 아래 3에서 설명한다.
Queue<Fish>[][] storeFishMap = new LinkedList[4][4];
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
storeFishMap[i][j] = new LinkedList<Fish>();
}
}
// System.out.println("===========fishMap==============");
// for(int i=0;i<4;i++) {
// for(int j=0;j<4;j++) {
// for(Fish temp : fishMap[i][j]) {
// System.out.print(" "+temp.r+" "+temp.c+" "+temp.dir);
// }
// }
// System.out.println();
// }
for(int i=0;i<4; i++) {
for(int j=0;j<4;j++) {
while(!fishMap[i][j].isEmpty()) {
boolean fishMoveFlag = false;
Fish temp = fishMap[i][j].poll();
int originDir = temp.dir;
for(int dir = 0; dir < 8; dir++) {
if(dir > 0) //처음 방향부터 먼저 검사합니다.
temp.dir = changeDirByAntiClock(temp.dir);
int nr = temp.r + dx[temp.dir];
int nc = temp.c + dy[temp.dir];
if(nr < 0 || nr >= 4 || nc < 0 || nc >= 4) continue; //격자의 범위를 벗어나는경우
if( nr == shark.r && nc == shark.c) continue; //상어가 존재하는 위치일경우
if( fishSmellVisited[nr][nc] > 0 ) continue; //물고기의 냄새가 남아있을경우
//한번 이동성공한다면 해당 물고기의 이동은 중단시킨다.
storeFishMap[nr][nc].offer(new Fish(nr, nc, temp.dir));
fishMoveFlag = true;
break;
}
if(fishMoveFlag == false) { //만약 이동할 수 없다면 이동을 하지 않는데, 그래도 위치에는 있어야한다.
storeFishMap[temp.r][temp.c].add(new Fish(temp.r, temp.c, originDir)); //이 부분에서 틀렸다.. 원래 방향으로유지해야햇다.
}
}
}
}
//얕은복사로 메모리 복사해온다.
fishMap = storeFishMap;
// System.out.println("===========After fishMoved ==============");
// for(int i=0;i<4;i++) {
// for(int j=0;j<4;j++) {
// for(Fish temp : fishMap[i][j]) {
// System.out.print(" "+temp.r+" "+temp.c+" "+temp.dir);
// }
// }
// System.out.println();
// }
//3. 상어가 연속해서 3칸 이동한다. 상어는 현재 칸에서 상하좌우로 인전합 칸으로 이동할 수 있다. 연속해서 이동하는 칸중에 격자의 범위를 벗어나는 칸이 있으면, 그 방법은 불가능한 이동방법이다.
//연속해서 이동하는 중에 상어가 물고기가 있는 같은 칸으로 이동하게 된다면, 그 칸에 있는 모든 물고기는 격자에서 제외되며, 제외되는 모든 물고기는 물고기 냄새를 남긴다.
// 가능한 이동 방법 중에서 제외되는 물고기의 수가 가장 많은 방법으로 이동하며, 그러한 방법이 여러가지인 경우 사전 순으로 가장 앞서는 방법을 이용한다.
//사전 순에 대한 문제의 하단 노트에 있다.
//모든 과정을 거쳐서 최대값을 구햇다면 실제로 상어가 먹히도록 처리한다.
//작업진행하며 바로바로 할 수도 있겠지만, 현재 자료구조형이 이러하기에 먼저 구하고 작업하도록 한다.
maxSharkEat = SHARKMININITIAL; //-1로 초기화한다.
maxSharkEatMove = new int[3];
sharkMovePermutation(0);
//
if(maxSharkEat >= 0)
sharkEatFish();
//두번 전 연습에서 생긴 물고기의 냄새가 격자에서 사라진다.
//이 로직은 3번 과정 직후에 격자에서 사라지기에 초기값은 3으로 처리했습니다.
// System.out.println("==========fishSmell================");
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
// System.out.print(" "+fishSmellVisited[i][j]);
if(fishSmellVisited[i][j] > 0)
fishSmellVisited[i][j] -= 1;
}
// System.out.println();
}
//1에서 사용한 복제마법이 완료된다. 모든 복제된 물고기는 1에서의 위치와 방향을 그대로 갖게된다.
while(!copyFishQueue.isEmpty()) {
Fish temp = copyFishQueue.poll();
fishMap[temp.r][temp.c].add(new Fish(temp.r, temp.c, temp.dir));
}
}
for(int i=0;i<4;i++) {
for(int j=0;j<4; j++) {
answer += fishMap[i][j].size();
}
}
System.out.println(answer);
}
//반시게방향이다.. 처음에 주어진 순서가 시계방향인줄 착각하고... 하..
public static int changeDirByAntiClock(int dir) {
return ( dir - 1 ) >= 0 ? dir - 1 : 7;
}
public static void sharkMovePermutation(int level) {
if(level == 3) {
int[][] fishSize = new int[4][4];
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
fishSize[i][j] = fishMap[i][j].size();
}
}
// System.out.println("==============");
// for(int i=0;i<3;i++) {
// System.out.print(" "+sharkMove[i]);
// }
// System.out.println("");
//가장 많이 갈 수 있는 방법을 구한다.
//연속해서 이동 중 격자의 범위를 벗어난다면, 그 방법은 불가능한 방법이다.
int sharkEatFish = 0;
int nsr = shark.r;
int nsc = shark.c;
for(int i=0; i<3;i++) {
nsr = nsr + sharkDx[sharkMove[i]];
nsc = nsc + sharkDy[sharkMove[i]];
//만약 격자범위를 벗어난다면, 이동못한다.
if(nsr < 0 || nsr >= 4 || nsc < 0 || nsc >= 4) {
sharkEatFish = OUTOFBOUND;
return ;
}
//만약 물고기를 만난다면 다 먹는다.
// for(Fish tempFish : fishMap[nsr][nsc]) {
// sharkEatFish += 1;
// }
sharkEatFish += fishSize[nsr][nsc];
fishSize[nsr][nsc] = 0;
}
//만약 최대상어가먹는양보다 더 많이 먹는다면, 해당 방법으로 갱신해준다.
if(maxSharkEat < sharkEatFish) {
maxSharkEat = sharkEatFish;
// System.out.println("maxsharkEat:"+maxSharkEat);
for(int i=0; i<3; i++) {
maxSharkEatMove[i] = sharkMove[i];
}
}
return ;
}
for(int dir = 0; dir < 4; dir++) {
sharkMove[level] = dir;
sharkMovePermutation(level + 1);
}
}
public static void sharkEatFish() {
// System.out.println("========sharkEatFish==============");
for(int dir = 0; dir < 3; dir++) {
// System.out.print(" "+maxSharkEatMove[dir]);
shark.r = shark.r + sharkDx[maxSharkEatMove[dir]];
shark.c = shark.c + sharkDy[maxSharkEatMove[dir]];
if(fishMap[shark.r][shark.c].size() > 0)
fishSmellVisited[shark.r][shark.c] = 3;
while(!fishMap[shark.r][shark.c].isEmpty()) {
fishMap[shark.r][shark.c].poll();
}
}
}
}
class Fish{
int r;
int c;
int dir;
public Fish(int r, int c, int dir) {
this.r = r;
this.c = c;
this.dir = dir;
}
}
class Shark{
int r;
int c;
int dir;
public Shark(int r, int c) {
this.r = r;
this.c = c;
}
public Shark(int r, int c, int dir) {
this.r = r;
this.c = c;
this.dir = dir;
}
}

+ Recent posts